From a44b02911772e8af475845a6a444d96774b6c876 Mon Sep 17 00:00:00 2001 From: Divyesh Shinde Date: Mon, 9 Jun 2025 15:11:57 +0530 Subject: [PATCH] NTP-64314 | Divyesh | Add bill options enhancement (#16214) --- .../com/navi/bbps/common/NaviBbpsAnalytics.kt | 126 ++++++- .../common/ui/NaviBbpsCommonComposable.kt | 200 ++++++++++- .../common/usecase/MyBillActionsHandler.kt | 106 ++++++ .../feature/billerlist/BillerListViewModel.kt | 319 ++++++++++++------ .../model/view/BillerListBottomSheetType.kt | 30 +- .../ui/BillerListBottomSheetContent.kt | 118 ++++--- .../feature/billerlist/ui/BillerListScreen.kt | 71 ++-- .../billerlist/ui/RenderBillerListScreen.kt | 78 ++--- .../category/BillCategoriesViewModel.kt | 120 ++++--- .../category/BillCategoryViewModelV2.kt | 3 + .../ui/BillCategoryBottomSheetContent.kt | 19 +- .../contactlist/ContactListViewModel.kt | 263 +++++++++++---- .../model/view/ContactListBottomSheetType.kt | 30 +- .../ui/ContactListBottomSheetContent.kt | 124 ++++--- .../contactlist/ui/ContactListScreen.kt | 75 ++-- .../contactlist/ui/RenderContactListScreen.kt | 117 ++----- .../bbps/feature/mybills/MyBillsViewModel.kt | 130 ++++--- .../mybills/ui/MyBillsBottomSheetContent.kt | 229 ++----------- .../navi-bbps/src/main/res/values/strings.xml | 1 + 19 files changed, 1363 insertions(+), 796 deletions(-) create mode 100644 android/navi-bbps/src/main/kotlin/com/navi/bbps/common/usecase/MyBillActionsHandler.kt diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/NaviBbpsAnalytics.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/NaviBbpsAnalytics.kt index 5c0d8b0792..45720f39a6 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/NaviBbpsAnalytics.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/NaviBbpsAnalytics.kt @@ -296,6 +296,48 @@ class NaviBbpsAnalytics private constructor() { ) } + fun billDeletionSuccess( + data: BbpsGenericResponse, + myBillEntity: MyBillEntity, + sessionAttribute: Map, + source: String, + initialSource: String, + ) { + NaviTrackEvent.trackEventOnClickStream( + eventName = "NaviBBPS_CategoryPage_DeletedBill_Success", + eventValues = + mapOf( + "data" to data.toString(), + "billId" to myBillEntity.billId, + "billerName" to myBillEntity.billerName, + NAVI_BBPS_SESSION_ID to sessionAttribute[NAVI_BBPS_SESSION_ID].orEmpty(), + NAVI_BBPS_SOURCE to source, + NAVI_BBPS_INITIAL_SOURCE to initialSource, + ), + ) + } + + fun billDeletionFailed( + deleteBillResponse: RepoResult, + myBillEntity: MyBillEntity, + sessionAttribute: Map, + source: String, + initialSource: String, + ) { + NaviTrackEvent.trackEventOnClickStream( + eventName = "NaviBBPS_CategoryPage_DeletedBill_Failed", + eventValues = + mapOf( + "deleteBillResponse" to deleteBillResponse.toString(), + "billId" to myBillEntity.billId, + "billerName" to myBillEntity.billerName, + NAVI_BBPS_SESSION_ID to sessionAttribute[NAVI_BBPS_SESSION_ID].orEmpty(), + NAVI_BBPS_SOURCE to source, + NAVI_BBPS_INITIAL_SOURCE to initialSource, + ), + ) + } + fun onUnpaidBillOptionsClicked( sessionAttribute: Map, billEntity: MyBillEntity, @@ -728,6 +770,25 @@ class NaviBbpsAnalytics private constructor() { ) } + fun onBillOptionsClicked( + sessionAttribute: Map, + billEntity: MyBillEntity, + action: String, + ) { + NaviTrackEvent.trackEventOnClickStream( + eventName = "NaviBBPS_BillerList_BillsOptions_Clicked", + eventValues = + mapOf( + "billId" to billEntity.billId, + "billerId" to billEntity.billerId, + "categoryId" to billEntity.categoryId, + "billerName" to billEntity.billerName, + "action" to action, + NAVI_BBPS_SESSION_ID to sessionAttribute[NAVI_BBPS_SESSION_ID].orEmpty(), + ), + ) + } + fun onNavigateToNextScreen( billCategoryEntity: BillCategoryEntity, route: String, @@ -848,7 +909,7 @@ class NaviBbpsAnalytics private constructor() { ) } - fun billDeletedSuccessfully( + fun billDeletionSuccess( data: BbpsGenericResponse, myBillEntity: MyBillEntity, sessionAttribute: Map, @@ -1658,6 +1719,25 @@ class NaviBbpsAnalytics private constructor() { ) } + fun onBillOptionsClicked( + sessionAttribute: Map, + billEntity: MyBillEntity, + action: String, + ) { + NaviTrackEvent.trackEventOnClickStream( + eventName = "NaviBBPS_ContactList_BillsOptions_Clicked", + eventValues = + mapOf( + "billId" to billEntity.billId, + "billerId" to billEntity.billerId, + "categoryId" to billEntity.categoryId, + "billerName" to billEntity.billerName, + "action" to action, + NAVI_BBPS_SESSION_ID to sessionAttribute[NAVI_BBPS_SESSION_ID].orEmpty(), + ), + ) + } + fun onDeleteAccountKebabMenuClicked( myBillEntity: MyBillEntity, sessionAttribute: Map, @@ -1711,7 +1791,7 @@ class NaviBbpsAnalytics private constructor() { ) } - fun billDeletedSuccessfully( + fun billDeletionSuccess( data: BbpsGenericResponse, myBillEntity: MyBillEntity, sessionAttribute: Map, @@ -2506,6 +2586,48 @@ class NaviBbpsAnalytics private constructor() { ) } + fun billDeletionSuccess( + data: BbpsGenericResponse, + myBillEntity: MyBillEntity, + sessionAttribute: Map, + source: String, + initialSource: String, + ) { + NaviTrackEvent.trackEventOnClickStream( + eventName = "NaviBBPS_MyBills_DeletedBill_Success", + eventValues = + mapOf( + "data" to data.toString(), + "billId" to myBillEntity.billId, + "billerName" to myBillEntity.billerName, + NAVI_BBPS_SESSION_ID to sessionAttribute[NAVI_BBPS_SESSION_ID].orEmpty(), + NAVI_BBPS_SOURCE to source, + NAVI_BBPS_INITIAL_SOURCE to initialSource, + ), + ) + } + + fun billDeletionFailed( + deleteBillResponse: RepoResult, + myBillEntity: MyBillEntity, + sessionAttribute: Map, + source: String, + initialSource: String, + ) { + NaviTrackEvent.trackEventOnClickStream( + eventName = "NaviBBPS_MyBills_DeletedBill_Failed", + eventValues = + mapOf( + "deleteBillResponse" to deleteBillResponse.toString(), + "billId" to myBillEntity.billId, + "billerName" to myBillEntity.billerName, + NAVI_BBPS_SESSION_ID to sessionAttribute[NAVI_BBPS_SESSION_ID].orEmpty(), + NAVI_BBPS_SOURCE to source, + NAVI_BBPS_INITIAL_SOURCE to initialSource, + ), + ) + } + fun onMyBillMoreOptionsBottomSheetClicked(billEntity: MyBillEntity, action: String) { NaviTrackEvent.trackEventOnClickStream( eventName = "NaviBBPS_MyBills_BottomSheet_Options_Clicked", diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/ui/NaviBbpsCommonComposable.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/ui/NaviBbpsCommonComposable.kt index 883ab8e05a..f2719bb8bb 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/ui/NaviBbpsCommonComposable.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/ui/NaviBbpsCommonComposable.kt @@ -12,6 +12,7 @@ import android.graphics.Rect import android.view.ViewTreeObserver import androidx.annotation.ColorRes import androidx.annotation.DrawableRes +import androidx.annotation.StringRes import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.core.animateFloatAsState @@ -125,7 +126,12 @@ import com.navi.bbps.common.BULLET import com.navi.bbps.common.CATEGORY_ID_CREDIT_CARD import com.navi.bbps.common.DASH import com.navi.bbps.common.DATE_TIME_FORMAT_DATE_MONTH_NAME +import com.navi.bbps.common.ICON_MARK_AS_PAID_UNPAID_BILL_BOTTOMSHEET +import com.navi.bbps.common.ICON_REFRESH_BILL_UNPAID_BILL_BOTTOMSHEET +import com.navi.bbps.common.ICON_REMOVE_ACCOUNT_UNPAID_BILL_BOTTOMSHEET +import com.navi.bbps.common.ICON_VIEW_BILL_HISTORY_UNPAID_BILL_BOTTOMSHEET import com.navi.bbps.common.NaviBbpsDimens +import com.navi.bbps.common.SYMBOL_RUPEE import com.navi.bbps.common.model.view.DetectedBillEntity import com.navi.bbps.common.model.view.NaviPermissionResult import com.navi.bbps.common.model.view.PrepaidPlanItemLoadingState @@ -1940,7 +1946,10 @@ fun BillAmountAndDueDateSectionWithShimmer( Spacer(modifier = Modifier.height(2.dp)) Box( modifier = - Modifier.padding(start = 48.dp).width(200.dp).height(20.dp).bbpsShimmerEffect() + Modifier.padding(start = if (startWithPadding) 48.dp else 0.dp) + .width(200.dp) + .height(20.dp) + .bbpsShimmerEffect() ) } else { if (myBillEntity.isBillPaid.not()) { @@ -2340,6 +2349,195 @@ fun OriginLandingWidget( } } +@Composable +fun BillOptionsBottomSheetContent( + billerLogoUrl: String, + title: String, + amount: String, + billerName: String, + expiringOn: String, + isBillPaid: Boolean = false, + isRechargeCategory: Boolean, + onMarkAsPaidClicked: () -> Unit, + onRefreshBillClicked: () -> Unit, + onViewBillHistoryClicked: () -> Unit, + onRemoveAccountClicked: () -> Unit, + onCloseClicked: () -> Unit, +) { + val descriptionText = buildAnnotatedString { + if (isBillPaid) { + append(billerName) + } else { + val fullText = stringResource(id = R.string.bbps_payment_due, billerName, expiringOn) + withStyle( + style = + SpanStyle( + fontFamily = naviFontFamily, + fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), + fontSize = 14.sp, + color = NaviBbpsColor.textTertiary, + ) + ) { + append(fullText) + } + addStyle( + style = + SpanStyle( + fontFamily = naviFontFamily, + fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), + fontSize = 14.sp, + color = NaviBbpsColor.inputFieldError, + ), + start = fullText.indexOf(expiringOn), + end = fullText.indexOf(expiringOn) + expiringOn.length, + ) + } + } + + Column( + modifier = + Modifier.navigationBarsPadding() + .fillMaxWidth() + .wrapContentHeight() + .padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 32.dp) + ) { + Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { + BbpsAsyncImage( + imageUrl = billerLogoUrl, + modifier = Modifier.size(24.dp), + placeholderIconResId = CommonR.drawable.navi_common_ic_biller_placeholder, + ) + Spacer(modifier = Modifier.weight(1f)) + Image( + painter = painterResource(id = CommonR.drawable.ic_close_black), + contentDescription = null, + modifier = Modifier.size(24.dp).clickableDebounce { onCloseClicked() }, + ) + } + Spacer(modifier = Modifier.height(8.dp)) + NaviText( + text = + buildString { + if (title.isNotBlank()) { + append(title) + append(com.navi.common.utils.SPACE) + } + if (!isBillPaid) { + if (title.isNotBlank() && amount.isNotBlank()) { + append(BULLET) + append(com.navi.common.utils.SPACE) + } + if (amount.isNotBlank()) { + append(SYMBOL_RUPEE) + append(amount) + } + } + }, + fontSize = 16.sp, + fontFamily = naviFontFamily, + fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD), + color = NaviBbpsColor.textPrimary, + ) + Spacer(modifier = Modifier.height(2.dp)) + + if (descriptionText.isNotBlank()) { + NaviText( + text = descriptionText, + fontSize = 14.sp, + fontFamily = naviFontFamily, + fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), + color = NaviBbpsColor.textTertiary, + lineHeight = 22.sp, + ) + } + + Spacer(modifier = Modifier.height(24.dp)) + + val actions = mutableListOf() + + if (!isBillPaid) { + actions.add( + ActionItem( + imageS3Url = ICON_MARK_AS_PAID_UNPAID_BILL_BOTTOMSHEET, + textResId = R.string.bbps_mark_as_paid, + onClick = onMarkAsPaidClicked, + ) + ) + } + if (!isRechargeCategory) { + actions.add( + ActionItem( + imageS3Url = ICON_REFRESH_BILL_UNPAID_BILL_BOTTOMSHEET, + textResId = R.string.bbps_refresh_bill, + onClick = onRefreshBillClicked, + ) + ) + } + actions.addAll( + listOf( + ActionItem( + imageS3Url = ICON_VIEW_BILL_HISTORY_UNPAID_BILL_BOTTOMSHEET, + textResId = + if (isRechargeCategory) { + R.string.bbps_view_recharge_history + } else R.string.bbps_view_bill_history, + onClick = onViewBillHistoryClicked, + ), + ActionItem( + imageS3Url = ICON_REMOVE_ACCOUNT_UNPAID_BILL_BOTTOMSHEET, + textResId = R.string.bbps_remove_account, + onClick = onRemoveAccountClicked, + ), + ) + ) + + actions.forEachIndexed { index, action -> + IconActionRow( + imageS3Url = action.imageS3Url, + text = stringResource(id = action.textResId), + onClick = action.onClick, + ) + + if (index < actions.lastIndex) { + HorizontalDivider( + modifier = Modifier.padding(vertical = 20.dp), + thickness = 1.dp, + color = NaviBbpsColor.borderAlt, + ) + } + } + } +} + +data class ActionItem( + val imageS3Url: String, + @StringRes val textResId: Int, + val onClick: () -> Unit, +) + +@Composable +fun IconActionRow( + imageS3Url: String, + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier.fillMaxWidth().noRippleClickableWithDebounce(onClick = onClick), + verticalAlignment = Alignment.CenterVertically, + ) { + AsyncImage(modifier = Modifier.size(24.dp), model = imageS3Url, contentDescription = "") + Spacer(modifier = Modifier.width(12.dp)) + NaviText( + text = text, + fontSize = 14.sp, + fontFamily = naviFontFamily, + fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), + color = NaviBbpsColor.textPrimary, + ) + } +} + @Preview @Composable fun OriginLandingWidgetPreview() { diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/usecase/MyBillActionsHandler.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/usecase/MyBillActionsHandler.kt new file mode 100644 index 0000000000..a6fc4bcbc7 --- /dev/null +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/usecase/MyBillActionsHandler.kt @@ -0,0 +1,106 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.bbps.common.usecase + +import com.navi.base.utils.NaviNetworkConnectivity +import com.navi.base.utils.retry +import com.navi.bbps.common.DEFAULT_RETRY_COUNT +import com.navi.bbps.common.RETRY_INTERVAL_IN_SECONDS +import com.navi.bbps.common.model.network.BbpsGenericResponse +import com.navi.bbps.common.repository.BbpsCommonRepository +import com.navi.bbps.common.utils.NaviBbpsCommonUtils.getBbpsMetricInfo +import com.navi.bbps.feature.customerinput.model.network.BillDetailsRequest +import com.navi.bbps.feature.customerinput.model.network.BillDetailsResponse +import com.navi.bbps.feature.customerinput.model.network.DeviceDetails +import com.navi.bbps.feature.mybills.MyBillsRepository +import com.navi.bbps.feature.mybills.MyBillsSyncJob +import com.navi.bbps.feature.mybills.model.view.MyBillEntity +import com.navi.common.network.models.RepoResult +import com.navi.common.network.models.isSuccessWithData +import javax.inject.Inject + +class MyBillActionsHandler +@Inject +constructor( + private val bbpsCommonRepository: BbpsCommonRepository, + private val naviNetworkConnectivity: NaviNetworkConnectivity, + private val myBillsRepository: MyBillsRepository, + private val myBillsSyncJob: MyBillsSyncJob, +) { + + suspend fun refreshBill( + myBillEntity: MyBillEntity, + screenName: String, + onSuccess: () -> Unit, + onError: suspend (RepoResult) -> Unit, + ) { + val billDetailsRequest = + BillDetailsRequest( + billerId = myBillEntity.billerId, + customerParams = myBillEntity.customerParams, + deviceDetails = DeviceDetails(ip = naviNetworkConnectivity.getIpAddress()), + amount = myBillEntity.actualLastPaidAmount, + isConsentProvided = true, + ) + + val billDetailsResponse = + bbpsCommonRepository.fetchBillDetails( + billDetailsRequest = billDetailsRequest, + metricInfo = getBbpsMetricInfo(screenName = screenName), + ) + + if (billDetailsResponse.isSuccessWithData() && billDetailsResponse.data != null) { + onSuccess() + } else { + onError(billDetailsResponse) + } + myBillsSyncJob.refreshBills(screenName = screenName) + } + + suspend fun deleteBill( + billId: String, + categoryId: String, + screenName: String, + onSuccess: suspend (RepoResult) -> Unit, + onError: suspend (RepoResult) -> Unit, + ) { + val savedBills = bbpsCommonRepository.fetchSavedBillsByCategory(category = categoryId) + if (savedBills.isNotEmpty()) { + val deleteBillResponse = + bbpsCommonRepository.deleteBill( + savedBillId = billId, + metricInfo = getBbpsMetricInfo(screenName = screenName), + ) + if (deleteBillResponse.isSuccessWithData()) { + myBillsSyncJob.refreshBills(screenName = screenName) + onSuccess(deleteBillResponse) + } else { + onError(deleteBillResponse) + } + } + } + + suspend fun markAsPaid(billId: String, screenName: String, onSuccess: () -> Unit) { + val response = + retry( + retryCount = DEFAULT_RETRY_COUNT, + retryIntervalInSeconds = RETRY_INTERVAL_IN_SECONDS, + execute = { + myBillsRepository.markBillAsPaid( + billId = billId, + metricInfo = getBbpsMetricInfo(screenName = screenName, isNae = { false }), + ) + }, + shouldRetry = { !it.isSuccessWithData() }, + ) + if (response.isSuccessWithData()) { + myBillsSyncJob.refreshBills(screenName = screenName) + onSuccess() + } + } +} 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 52e0a55af3..d4234d1625 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 @@ -12,8 +12,8 @@ import androidx.lifecycle.viewModelScope import com.google.firebase.crashlytics.FirebaseCrashlytics import com.navi.base.utils.BaseUtils import com.navi.base.utils.EMPTY +import com.navi.base.utils.NaviNetworkConnectivity import com.navi.base.utils.ResourceProvider -import com.navi.base.utils.isNotNull import com.navi.bbps.R import com.navi.bbps.common.CATEGORY_ID_FASTAG import com.navi.bbps.common.CATEGORY_ID_MOBILE_POSTPAID @@ -23,9 +23,11 @@ import com.navi.bbps.common.NaviBbpsScreen import com.navi.bbps.common.model.NaviBbpsVmData import com.navi.bbps.common.model.config.NaviBbpsDefaultConfig import com.navi.bbps.common.model.view.NaviPermissionResult +import com.navi.bbps.common.model.view.RefreshBillState import com.navi.bbps.common.repository.BbpsCommonRepository import com.navi.bbps.common.session.NaviBbpsSessionHelper import com.navi.bbps.common.usecase.FindLastOrderWithSuccessfulPaymentUseCase +import com.navi.bbps.common.usecase.MyBillActionsHandler import com.navi.bbps.common.usecase.NaviBbpsConfigUseCase import com.navi.bbps.common.usecase.RewardNudgeUseCase import com.navi.bbps.common.utils.BillDetailsResponseToEntityMapper @@ -48,8 +50,9 @@ import com.navi.bbps.feature.contactlist.model.view.PhoneContactEntity import com.navi.bbps.feature.customerinput.model.view.BillerDetailsEntity import com.navi.bbps.feature.customerinput.model.view.CustomerParamsExtraData import com.navi.bbps.feature.destinations.CustomerDataInputScreenDestination +import com.navi.bbps.feature.destinations.MyBillHistoryDetailsScreenDestination import com.navi.bbps.feature.destinations.PayBillScreenDestination -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.isRedirectToCustomerInputRequired @@ -96,14 +99,16 @@ constructor( private val bbpsCommonRepository: BbpsCommonRepository, private val naviBbpsConfigUseCase: NaviBbpsConfigUseCase, private val deviceLocationProvider: DeviceLocationProvider, - private val myBillsSyncJob: MyBillsSyncJob, private val rewardsNudgeEntityFetchUseCase: RewardNudgeUseCase, private val naviBbpsSessionHelper: NaviBbpsSessionHelper, private val myBillEntityToBillDetailsResponseMapper: MyBillEntityToBillDetailsResponseMapper, private val billDetailsResponseToEntityMapper: BillDetailsResponseToEntityMapper, private val resourceProvider: ResourceProvider, private val billerItemResponseToEntityMapper: BillerItemResponseToEntityMapper, - private val findLastOrderWithSuccessfulPaymentUseCase: FindLastOrderWithSuccessfulPaymentUseCase, + private val findLastOrderWithSuccessfulPaymentUseCase: + FindLastOrderWithSuccessfulPaymentUseCase, + private val naviNetworkConnectivity: NaviNetworkConnectivity, + private val myBillActionsHandler: MyBillActionsHandler, ) : NaviBbpsBaseVM(naviBbpsVmData = NaviBbpsVmData(screen = NaviBbpsScreen.NAVI_BBPS_BILLER_LIST)) { private val naviBbpsAnalytics: NaviBbpsAnalytics.BillerList = @@ -130,15 +135,11 @@ constructor( val rewardsNudgeDetailEntity = _rewardsNudgeDetailEntity.asStateFlow() private val _billerListBottomSheetType = - MutableStateFlow( - BillerListBottomSheetType.MenuOptions( - BillerListBottomSheetType.createEmptyMyBillEntity() - ) - ) + MutableStateFlow(BillerListBottomSheetType.None) val billerListBottomSheetType = _billerListBottomSheetType.asStateFlow() - private val _showSnackBar = MutableStateFlow(false) - val showSnackBar = _showSnackBar.asStateFlow() + private val _refreshBillItemAndStatus = MutableStateFlow(RefreshBillState()) + val refreshBillItemAndStatus = _refreshBillItemAndStatus.asStateFlow() val phoneNumber = BaseUtils.getPhoneNumber().toString() val normalisedPhoneNumber = getNormalisedPhoneNumber(phoneNumber = phoneNumber) @@ -184,6 +185,9 @@ constructor( private val _naviBbpsDefaultConfig = MutableStateFlow(NaviBbpsDefaultConfig()) val naviBbpsDefaultConfig = _naviBbpsDefaultConfig.asStateFlow() + private val _snackBarState = MutableStateFlow(SnackBarState(show = false)) + val snackBarState = _snackBarState.asStateFlow() + private var recentBillsEntity = RecentBillsEntity(title = "", bills = listOf()) val isTrailingIconEnabled = @@ -236,8 +240,12 @@ constructor( _navigateToNextScreen.emit(direction) } - fun updateSnackBarState(showSnackBar: Boolean) { - _showSnackBar.update { showSnackBar } + fun updateSnackBarState(show: Boolean, messageId: Int = R.string.bbps_copied_to_clipboard) { + _snackBarState.update { SnackBarState(show = show, messageId = messageId) } + } + + private fun updateRefreshState(isRefreshing: Boolean, bill: MyBillEntity? = null) { + _refreshBillItemAndStatus.value = RefreshBillState(isRefreshing, bill) } private val _permissionResult = @@ -290,25 +298,128 @@ constructor( } } - fun onDeleteMenuClicked(myBillEntity: MyBillEntity) { - _billerListBottomSheetType.update { BillerListBottomSheetType.MenuOptions(myBillEntity) } + fun onBillOptionsKebabMenuClicked(myBillEntity: MyBillEntity, openSheet: () -> Unit) { + _billerListBottomSheetType.update { + BillerListBottomSheetType.MenuOptions( + myBillEntity = myBillEntity, + onMarkAsPaidClicked = { + naviBbpsAnalytics.onBillOptionsClicked( + sessionAttribute = getNaviBbpsSessionAttributes(), + billEntity = myBillEntity, + action = resourceProvider.getString(R.string.bbps_mark_as_paid), + ) + viewModelScope.launch { + delay(200) + /* this is added to make sure the bottom sheet is closed before + opening the new one and to make the transition smoother */ + _billerListBottomSheetType.update { + BillerListBottomSheetType.MarkAsPaid( + myBillEntity = myBillEntity, + title = + resourceProvider.getString( + R.string.bbps_bill_mark_as_paid_heading + ), + description = + resourceProvider.getString( + R.string.bbps_bill_mark_as_paid_description + ), + firstBtnText = resourceProvider.getString(R.string.bbps_no), + secondButtonText = resourceProvider.getString(R.string.bbps_yes), + onFirstBtnClick = {}, + onSecondBtnClick = { + onMarkBillAsPaidConfirmedCtaClicked(myBillEntity = myBillEntity) + }, + ) + } + openSheet.invoke() + } + }, + onRefreshBillClicked = { + naviBbpsAnalytics.onBillOptionsClicked( + sessionAttribute = getNaviBbpsSessionAttributes(), + billEntity = myBillEntity, + action = resourceProvider.getString(R.string.bbps_refresh_bill), + ) + viewModelScope.launch(dispatcherProvider.io) { + if (!naviNetworkConnectivity.isInternetConnected()) { + notifyError(getNoInternetErrorConfig()) + return@launch + } else { + refreshBillAndUpdateInfo(myBillEntity = myBillEntity) + } + } + }, + onViewBillHistoryClicked = { + naviBbpsAnalytics.onBillOptionsClicked( + sessionAttribute = getNaviBbpsSessionAttributes(), + billEntity = myBillEntity, + action = resourceProvider.getString(R.string.bbps_view_bill_history), + ) + viewModelScope.launch { + delay(200) + _navigateToNextScreen.emit( + MyBillHistoryDetailsScreenDestination( + myBillEntity = myBillEntity, + source = naviBbpsVmData.screen.name, + initialSource = initialSource, + ) + ) + } + }, + onRemoveAccountClicked = { + naviBbpsAnalytics.onBillOptionsClicked( + sessionAttribute = getNaviBbpsSessionAttributes(), + billEntity = myBillEntity, + action = resourceProvider.getString(R.string.bbps_remove_account), + ) + viewModelScope.launch { + delay(200) + _billerListBottomSheetType.update { + BillerListBottomSheetType.RemoveAccount( + myBillEntity = myBillEntity, + title = + naviBbpsDefaultConfig.value.configMessage + .removeAccountBottomSheetTitle, + description = + naviBbpsDefaultConfig.value.configMessage + .removeAccountBottomSheetMessage, + firstBtnTextResId = R.string.bbps_cancel, + secondButtonTextResId = R.string.bbps_remove, + onFirstBtnClick = {}, + onSecondBtnClick = { deleteBill(myBillEntity) }, + ) + } + openSheet.invoke() + } + }, + ) + } } - fun onDeleteAccountBillClicked(myBillEntity: MyBillEntity) { - viewModelScope.launch { - delay(100) // for smooth transition of next bottom sheet - _billerListBottomSheetType.update { - BillerListBottomSheetType.Confirmation( - title = naviBbpsDefaultConfig.value.configMessage.removeAccountBottomSheetTitle, - description = - naviBbpsDefaultConfig.value.configMessage.removeAccountBottomSheetMessage, - firstBtnTextResId = R.string.bbps_cancel, - secondButtonTextResId = R.string.bbps_remove, - onFirstBtnClick = {}, - onSecondBtnClick = { deleteBill(myBillEntity) }, + private suspend fun refreshBillAndUpdateInfo(myBillEntity: MyBillEntity) { + updateRefreshState(isRefreshing = true, bill = myBillEntity) + myBillActionsHandler.refreshBill( + myBillEntity = myBillEntity, + screenName = naviBbpsVmData.screen.screenName, + onSuccess = { + updateSnackBarState( + show = true, + messageId = R.string.bbps_bill_refreshed_sucessfully, ) - } - } + }, + onError = { response -> + val errorCode = getError(response).code + if (errorCode in naviBbpsDefaultConfig.value.billAlreadyPaidErrorCodes) { + notifyError(response = response) + } else { + updateSnackBarState( + show = true, + messageId = R.string.bbps_bill_refreshed_failed, + ) + } + }, + ) + updateRefreshState(isRefreshing = false, bill = myBillEntity) } private suspend fun updateRecentBillsEntity() { @@ -321,55 +432,91 @@ constructor( } private fun deleteBill(myBillEntity: MyBillEntity) { - viewModelScope.launch(Dispatchers.IO) { - val savedBills = - bbpsCommonRepository.fetchSavedBillsByCategory( - category = billCategoryEntity.categoryId - ) - - if (savedBills.isNotEmpty()) { - if (myBillEntity.isNotNull()) { - val deleteBillResponse = - bbpsCommonRepository.deleteBill( - myBillEntity.billId, - metricInfo = getBbpsMetricInfo(screenName = naviBbpsVmData.screen.name), - ) - if (deleteBillResponse.isSuccessWithData()) { - myBillsSyncJob.refreshBills(naviBbpsVmData.screen.screenName) - updateRecentBillsEntity() - 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, - ) + viewModelScope.launch(dispatcherProvider.io) { + myBillActionsHandler.deleteBill( + billId = myBillEntity.billId, + categoryId = billCategoryEntity.categoryId, + screenName = naviBbpsVmData.screen.screenName, + onSuccess = { deleteBillResponse -> + updateRecentBillsEntity() + 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, ) - } - updateSnackBarState(showSnackBar = true) - naviBbpsAnalytics.billDeletedSuccessfully( - data = deleteBillResponse.data!!, - myBillEntity = myBillEntity, - sessionAttribute = getNaviBbpsSessionAttributes(), - source = source, - initialSource = initialSource, ) - } else { - notifyError(deleteBillResponse) - naviBbpsAnalytics.billDeletionFailed( - deleteBillResponse = deleteBillResponse, - myBillEntity = myBillEntity, - sessionAttribute = getNaviBbpsSessionAttributes(), - source = source, - initialSource = initialSource, + } + + updateSnackBarState( + show = true, + messageId = R.string.bbps_account_removed_successfully, + ) + naviBbpsAnalytics.billDeletionSuccess( + data = deleteBillResponse.data!!, + myBillEntity = myBillEntity, + sessionAttribute = getNaviBbpsSessionAttributes(), + source = source, + initialSource = initialSource, + ) + }, + onError = { deleteBillResponse -> + notifyError(deleteBillResponse) + naviBbpsAnalytics.billDeletionFailed( + deleteBillResponse = deleteBillResponse, + myBillEntity = myBillEntity, + sessionAttribute = getNaviBbpsSessionAttributes(), + source = source, + initialSource = initialSource, + ) + }, + ) + } + } + + 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 + } + myBillActionsHandler.markAsPaid( + billId = myBillEntity.billId, + screenName = naviBbpsVmData.screen.screenName, + onSuccess = { + updateSnackBarState(show = true, messageId = R.string.bbps_bill_marked_as_paid) + }, + ) + } + } + + private fun observeRecentBills() { + viewModelScope.launch(dispatcherProvider.io) { + bbpsCommonRepository + .fetchSavedBillsByCategoryAsFlow(categoryId = billCategoryEntity.categoryId) + .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, + ) ) } } - } } } @@ -587,32 +734,6 @@ 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/billerlist/model/view/BillerListBottomSheetType.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/model/view/BillerListBottomSheetType.kt index c75f6dab0d..59e55a842d 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/model/view/BillerListBottomSheetType.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/model/view/BillerListBottomSheetType.kt @@ -13,7 +13,33 @@ import com.navi.rr.common.models.CoinBurnData import com.navi.rr.common.models.OfferData sealed class BillerListBottomSheetType { - data class MenuOptions(val myBillEntity: MyBillEntity) : BillerListBottomSheetType() + data class MenuOptions( + val myBillEntity: MyBillEntity, + val onMarkAsPaidClicked: () -> Unit, + val onRefreshBillClicked: () -> Unit, + val onViewBillHistoryClicked: () -> Unit, + val onRemoveAccountClicked: () -> Unit, + ) : BillerListBottomSheetType() + + data class MarkAsPaid( + val myBillEntity: MyBillEntity, + val title: String, + val description: String? = null, + val firstBtnText: String, + val secondButtonText: String, + val onFirstBtnClick: () -> Unit, + val onSecondBtnClick: () -> Unit, + ) : BillerListBottomSheetType() + + data class RemoveAccount( + val myBillEntity: MyBillEntity, + val title: String, + val description: String? = null, + val firstBtnTextResId: Int, + val secondButtonTextResId: Int, + val onFirstBtnClick: () -> Unit, + val onSecondBtnClick: () -> Unit, + ) : BillerListBottomSheetType() data object FastagBankDetails : BillerListBottomSheetType() @@ -32,6 +58,8 @@ sealed class BillerListBottomSheetType { val coinBurnData: CoinBurnData?, ) : BillerListBottomSheetType() + data object None : BillerListBottomSheetType() + companion object { fun createEmptyMyBillEntity(): MyBillEntity { return MyBillEntity( diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/ui/BillerListBottomSheetContent.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/ui/BillerListBottomSheetContent.kt index c1a1c34fd3..18b3cd71c4 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/ui/BillerListBottomSheetContent.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/ui/BillerListBottomSheetContent.kt @@ -8,19 +8,13 @@ package com.navi.bbps.feature.billerlist.ui import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement 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.layout.wrapContentHeight import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.Start import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale @@ -34,11 +28,16 @@ import com.navi.bbps.common.ICON_FASTAG_BOTTOMSHEET import com.navi.bbps.common.NaviBbpsAnalytics import com.navi.bbps.common.theme.NaviBbpsColor import com.navi.bbps.common.ui.BbpsOfferBottomSheet +import com.navi.bbps.common.ui.BillOptionsBottomSheetContent import com.navi.bbps.common.ui.ConfirmationBottomSheetContent import com.navi.bbps.common.ui.ThemeRoundedButton +import com.navi.bbps.common.utils.NaviBbpsCommonUtils.isRechargeCategory import com.navi.bbps.feature.billerlist.model.view.BillerListBottomSheetType -import com.navi.bbps.feature.mybills.model.view.MyBillEntity +import com.navi.bbps.feature.mybills.ui.MarkAsPaidBottomSheetContent +import com.navi.bbps.feature.mybills.ui.RemoveAccountBottomSheetContent +import com.navi.bbps.getBillTitleFromAccountHolderNameOrPrimaryCustomerParams import com.navi.common.R as CommonR +import com.navi.common.utils.firstLetterToLowerCase import com.navi.design.font.FontWeightEnum import com.navi.design.font.getFontWeight import com.navi.design.font.naviFontFamily @@ -48,7 +47,6 @@ import com.navi.naviwidgets.extensions.NaviText fun BillerListBottomSheetContent( billerListBottomSheetType: BillerListBottomSheetType, closeSheet: () -> Unit, - onDeleteAccountBillClicked: (MyBillEntity) -> Unit, naviBbpsAnalytics: NaviBbpsAnalytics.BillerList, sessionAttribute: Map, source: String, @@ -56,10 +54,45 @@ fun BillerListBottomSheetContent( ) { when (billerListBottomSheetType) { is BillerListBottomSheetType.MenuOptions -> { - BillerListMenuBottomSheetContent( - onDeleteAccountBillClicked = { - onDeleteAccountBillClicked.invoke(billerListBottomSheetType.myBillEntity) - } + BillOptionsBottomSheetContent( + billerLogoUrl = billerListBottomSheetType.myBillEntity.billerLogoUrl, + title = + getBillTitleFromAccountHolderNameOrPrimaryCustomerParams( + accountHolderName = + billerListBottomSheetType.myBillEntity.unpaidBillDetails + ?.accountHolderName, + primaryCustomerParams = + billerListBottomSheetType.myBillEntity.primaryCustomerParamValue, + ), + expiringOn = + billerListBottomSheetType.myBillEntity.unpaidBillWarning + .firstLetterToLowerCase(), + amount = billerListBottomSheetType.myBillEntity.unpaidBillDetails?.amount.orEmpty(), + billerName = billerListBottomSheetType.myBillEntity.billerName, + onRemoveAccountClicked = { + closeSheet() + billerListBottomSheetType.onRemoveAccountClicked() + }, + onCloseClicked = { closeSheet() }, + isBillPaid = billerListBottomSheetType.myBillEntity.isBillPaid, + isRechargeCategory = + isRechargeCategory( + categoryId = billerListBottomSheetType.myBillEntity.categoryId + ), + onMarkAsPaidClicked = { + closeSheet() + if (!billerListBottomSheetType.myBillEntity.isBillPaid) { + billerListBottomSheetType.onMarkAsPaidClicked() + } + }, + onRefreshBillClicked = { + closeSheet() + billerListBottomSheetType.onRefreshBillClicked() + }, + onViewBillHistoryClicked = { + closeSheet() + billerListBottomSheetType.onViewBillHistoryClicked() + }, ) } is BillerListBottomSheetType.FastagBankDetails -> { @@ -102,38 +135,43 @@ fun BillerListBottomSheetContent( closeSheet = closeSheet, ) } - } -} - -@Composable -fun BillerListMenuBottomSheetContent(onDeleteAccountBillClicked: () -> Unit) { - Column( - modifier = - Modifier.fillMaxWidth() - .wrapContentHeight() - .padding(start = 16.dp, top = 16.dp, end = 16.dp, bottom = 32.dp) - ) { - Row( - modifier = - Modifier.fillMaxWidth().padding(vertical = 16.dp).clickable { - onDeleteAccountBillClicked() + is BillerListBottomSheetType.MarkAsPaid -> { + MarkAsPaidBottomSheetContent( + title = billerListBottomSheetType.title, + description = billerListBottomSheetType.description, + secondaryButtonText = billerListBottomSheetType.firstBtnText, + primaryButtonText = billerListBottomSheetType.secondButtonText, + onSecondaryButtonClicked = { + closeSheet() + billerListBottomSheetType.onFirstBtnClick() + }, + onPrimaryButtonClicked = { + closeSheet() + billerListBottomSheetType.onSecondBtnClick() }, - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically, - ) { - Image( - painter = painterResource(id = R.drawable.ic_remove_account), - contentDescription = null, ) - Spacer(modifier = Modifier.width(14.dp)) - NaviText( - text = stringResource(id = R.string.bbps_remove_account), - fontSize = 14.sp, - fontFamily = naviFontFamily, - fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), - color = NaviBbpsColor.textPrimary, + } + is BillerListBottomSheetType.RemoveAccount -> { + RemoveAccountBottomSheetContent( + title = billerListBottomSheetType.title, + description = billerListBottomSheetType.description, + secondaryButtonText = + stringResource(id = billerListBottomSheetType.firstBtnTextResId), + primaryButtonText = + stringResource(id = billerListBottomSheetType.secondButtonTextResId), + onSecondaryButtonClicked = { + closeSheet() + billerListBottomSheetType.onFirstBtnClick() + }, + onPrimaryButtonClicked = { + closeSheet() + billerListBottomSheetType.onSecondBtnClick() + }, ) } + is BillerListBottomSheetType.None -> { + // Do nothing + } } } diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/ui/BillerListScreen.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/ui/BillerListScreen.kt index d8f538c905..d8731c05d2 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/ui/BillerListScreen.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/ui/BillerListScreen.kt @@ -77,6 +77,7 @@ import com.navi.bbps.initials import com.navi.design.font.FontWeightEnum import com.navi.design.font.getFontWeight import com.navi.design.font.naviFontFamily +import com.navi.design.snackbar.ErrorSnackBar import com.navi.design.snackbar.SuccessSnackBar import com.navi.naviwidgets.extensions.NaviText import com.ramcosta.composedestinations.annotation.Destination @@ -116,7 +117,6 @@ fun BillerListScreen( billerListViewModel.showSpaceBelowSearchBar.collectAsStateWithLifecycle() val billerListBottomSheetType by billerListViewModel.billerListBottomSheetType.collectAsStateWithLifecycle() - val showSnackBar by billerListViewModel.showSnackBar.collectAsStateWithLifecycle() val bbpsSnackBarPredefinedConfig = remember { BbpsSnackBarPredefinedConfig() } val permissionResult by billerListViewModel.permissionResult.collectAsStateWithLifecycle() @@ -126,6 +126,9 @@ fun BillerListScreen( val coinBurnData by billerListViewModel.coinBurnData.collectAsStateWithLifecycle() val multipleOffersDataList by billerListViewModel.multipleOffersDataList.collectAsStateWithLifecycle() + val refreshBillItemAndStatus by + billerListViewModel.refreshBillItemAndStatus.collectAsStateWithLifecycle() + val snackBarState by billerListViewModel.snackBarState.collectAsStateWithLifecycle() LaunchedEffect(Unit) { naviBbpsAnalytics.onLanded( @@ -214,11 +217,6 @@ fun BillerListScreen( billerListViewModel.onRecentBillItemClicked(myBillEntity = myBillEntity) } - val onDeleteAccountBillClicked = { myBillEntity: MyBillEntity -> - billerListViewModel.onDeleteAccountBillClicked(myBillEntity) - openSheet() - } - val onBackClick = { naviBbpsAnalytics.onBackClicked( billCategoryEntity = billCategoryEntity, @@ -348,7 +346,6 @@ fun BillerListScreen( sheetContent = { BillerListBottomSheetContent( billerListBottomSheetType = billerListBottomSheetType, - onDeleteAccountBillClicked = onDeleteAccountBillClicked, closeSheet = closeSheet, naviBbpsAnalytics = naviBbpsAnalytics, sessionAttribute = billerListViewModel.getNaviBbpsSessionAttributes(), @@ -466,7 +463,7 @@ fun BillerListScreen( ) onRecentBillItemClicked(it) }, - onDeleteRecentBillClicked = { + onBillOptionsKebabMenuClicked = { naviBbpsAnalytics.onDeleteAccountKebabMenuClicked( myBillEntity = it, sessionAttribute = @@ -474,7 +471,10 @@ fun BillerListScreen( source = source, initialSource = initialSource, ) - billerListViewModel.onDeleteMenuClicked(myBillEntity = it) + billerListViewModel.onBillOptionsKebabMenuClicked( + myBillEntity = it, + openSheet = openSheet, + ) openSheet() }, isSearchBillerRunning = isSearchBillerRunning, @@ -482,6 +482,7 @@ fun BillerListScreen( fetchLocationPermissionState.allPermissionsGranted, naviBbpsAnalytics = naviBbpsAnalytics, multipleOffersDataList = multipleOffersDataList, + refreshBillItemAndStatus = refreshBillItemAndStatus, onFailedOrderCalloutClicked = { val failedOrder = it.first val billDetails = it.second @@ -523,23 +524,43 @@ fun BillerListScreen( } }, snackbarHost = { - if (showSnackBar) { + if (snackBarState.show) { Column { - SuccessSnackBar( - modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), - show = true, - snackBarConfig = - bbpsSnackBarPredefinedConfig.successConfig( - title = - stringResource( - id = R.string.bbps_account_removed_successfully - ) - ), - onDismissed = { billerListViewModel.updateSnackBarState(false) }, - onTrailingIconClicked = { - billerListViewModel.updateSnackBarState(false) - }, - ) + if (snackBarState.messageId == R.string.bbps_bill_refreshed_failed) { + ErrorSnackBar( + modifier = + Modifier.fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 32.dp), + show = true, + snackBarConfig = + bbpsSnackBarPredefinedConfig.errorConfig( + title = stringResource(id = snackBarState.messageId) + ), + onDismissed = { + billerListViewModel.updateSnackBarState(show = false) + }, + onTrailingIconClicked = { + billerListViewModel.updateSnackBarState(show = false) + }, + ) + } else { + SuccessSnackBar( + modifier = + Modifier.fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 32.dp), + show = true, + snackBarConfig = + bbpsSnackBarPredefinedConfig.successConfig( + title = stringResource(id = snackBarState.messageId) + ), + onDismissed = { + billerListViewModel.updateSnackBarState(show = false) + }, + onTrailingIconClicked = { + billerListViewModel.updateSnackBarState(show = false) + }, + ) + } Spacer(modifier = Modifier.height(16.dp)) } diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/ui/RenderBillerListScreen.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/ui/RenderBillerListScreen.kt index 95e1bc9a73..8100f7e723 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/ui/RenderBillerListScreen.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/ui/RenderBillerListScreen.kt @@ -12,6 +12,7 @@ import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.slideOutHorizontally +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -60,7 +61,6 @@ import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.navi.bbps.R import com.navi.bbps.common.BILL_ITEM_SLIDE_OUT_DURATION -import com.navi.bbps.common.BULLET import com.navi.bbps.common.NaviBbpsAnalytics import com.navi.bbps.common.NaviBbpsDimens import com.navi.bbps.common.model.view.RefreshBillState @@ -99,6 +99,7 @@ import com.navi.rr.common.models.OfferData import kotlin.math.ceil import kotlinx.coroutines.launch +@OptIn(ExperimentalFoundationApi::class) @Composable fun RenderBillerListScreen( billerListState: BillerListState.Loaded, @@ -108,11 +109,12 @@ fun RenderBillerListScreen( openSheet: () -> Unit, onBillerItemClicked: (BillerItemEntity) -> Unit, onRecentBillItemClicked: (MyBillEntity) -> Unit, - onDeleteRecentBillClicked: (MyBillEntity) -> Unit, + onBillOptionsKebabMenuClicked: (MyBillEntity) -> Unit, isSearchBillerRunning: Boolean, isLocationPermissionProvided: Boolean, naviBbpsAnalytics: NaviBbpsAnalytics.BillerList, multipleOffersDataList: Map>?, + refreshBillItemAndStatus: RefreshBillState, onFailedOrderCalloutClicked: (Pair) -> Unit, ) { val isScreenFastagRecharge by @@ -217,6 +219,17 @@ fun RenderBillerListScreen( mutableStateOf(billerListState.recentBills.bills.toList()) } val vmRecentBills = rememberUpdatedState(billerListState.recentBills.bills) + + fun updateAnimatedRecentBills() { + animatedRecentBills = vmRecentBills.value.toList() + } + + LaunchedEffect(vmRecentBills.value) { + if (animatedRecentBills.size == vmRecentBills.value.size) { + updateAnimatedRecentBills() + } + } + LazyColumn( modifier = Modifier.fillMaxSize(), flingBehavior = maxScrollFlingBehavior(), @@ -301,14 +314,15 @@ fun RenderBillerListScreen( RecentBillerItem( myBillEntity = billItem, onRecentBillItemClicked = { onRecentBillItemClicked(billItem) }, - onDeleteRecentBillClicked = { - onDeleteRecentBillClicked(billItem) + onBillOptionsKebabMenuClicked = { + onBillOptionsKebabMenuClicked(billItem) }, offerData = getOfferDataForMultipleBillItems( multipleOffersDataList = multipleOffersDataList, billId = billItem.billId, ), + refreshBillItemAndStatus = refreshBillItemAndStatus, failedOrder = failedOrder, onFailedOrderCalloutClicked = onFailedOrderCalloutClicked, ) @@ -454,10 +468,11 @@ fun RecentBillerItem( modifier: Modifier = Modifier, myBillEntity: MyBillEntity, onRecentBillItemClicked: () -> Unit, - onDeleteRecentBillClicked: (MyBillEntity) -> Unit, + onBillOptionsKebabMenuClicked: (MyBillEntity) -> Unit, offerData: OfferData?, failedOrder: OrderEntity? = null, onFailedOrderCalloutClicked: (Pair) -> Unit, + refreshBillItemAndStatus: RefreshBillState, ) { Box(modifier = modifier) { Column( @@ -491,52 +506,11 @@ fun RecentBillerItem( Spacer(modifier = Modifier.height(2.dp)) BillerNameWithPrimaryCustomerParamValueSection(myBillEntity = myBillEntity) Spacer(modifier = Modifier.height(2.dp)) - - if (myBillEntity.isBillPaid) { - Row(verticalAlignment = Alignment.CenterVertically) { - if (myBillEntity.formattedLastPaidAmount.isNotBlank()) { - NaviText( - text = - stringResource( - id = R.string.bbps_rupee_symbol_x, - myBillEntity.formattedLastPaidAmount, - ), - color = NaviBbpsColor.textPrimary, - fontFamily = naviFontFamily, - fontSize = 14.sp, - fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD), - overflow = TextOverflow.Ellipsis, - ) - } - if (myBillEntity.formattedLastPaidDate.isNotBlank()) { - Spacer(modifier = Modifier.width(4.dp)) - NaviText( - text = BULLET, - color = NaviBbpsColor.textPrimary, - fontFamily = naviFontFamily, - fontSize = 16.sp, - fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), - ) - Spacer(modifier = Modifier.width(4.dp)) - NaviText( - text = - "${stringResource(id = R.string.bbps_last_paid)} ${myBillEntity.formattedLastPaidDate}", - color = NaviBbpsColor.textTertiary, - fontFamily = naviFontFamily, - fontSize = 12.sp, - fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), - overflow = TextOverflow.Ellipsis, - maxLines = 1, - ) - } - } - } else { - BillAmountAndDueDateSectionWithShimmer( - myBillEntity = myBillEntity, - refreshBillItemAndStatus = RefreshBillState(), - startWithPadding = false, - ) - } + BillAmountAndDueDateSectionWithShimmer( + myBillEntity = myBillEntity, + refreshBillItemAndStatus = refreshBillItemAndStatus, + startWithPadding = false, + ) } Spacer(modifier = Modifier.width(16.dp)) @@ -576,7 +550,7 @@ fun RecentBillerItem( Modifier.padding(top = 6.dp) .size(24.dp) .align(Alignment.Top) - .clickableDebounce { onDeleteRecentBillClicked(myBillEntity) }, + .clickableDebounce { onBillOptionsKebabMenuClicked(myBillEntity) }, ) } if (!myBillEntity.isBillPaid) { 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 48a421b722..31c9871ae1 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 @@ -19,18 +19,15 @@ import com.navi.base.utils.ResourceProvider import com.navi.base.utils.ZERO_STRING import com.navi.base.utils.isNotNull import com.navi.base.utils.orFalse -import com.navi.base.utils.retry import com.navi.bbps.R import com.navi.bbps.common.APP_VERSION_CODE import com.navi.bbps.common.BILLER_UNIQUE_ID import com.navi.bbps.common.BbpsSharedPreferences import com.navi.bbps.common.CATEGORY_ID_MOBILE_PREPAID import com.navi.bbps.common.CoinsSyncManager -import com.navi.bbps.common.DEFAULT_RETRY_COUNT import com.navi.bbps.common.NaviBbpsAnalytics import com.navi.bbps.common.NaviBbpsScreen import com.navi.bbps.common.RCBP_CATEGORY -import com.navi.bbps.common.RETRY_INTERVAL_IN_SECONDS import com.navi.bbps.common.TXN_AMOUNT import com.navi.bbps.common.model.NaviBbpsVmData import com.navi.bbps.common.model.config.NaviBbpsDefaultConfig @@ -38,6 +35,7 @@ import com.navi.bbps.common.model.view.RefreshBillState import com.navi.bbps.common.repository.BbpsCommonRepository import com.navi.bbps.common.session.NaviBbpsSessionHelper import com.navi.bbps.common.usecase.FindLastOrderWithSuccessfulPaymentUseCase +import com.navi.bbps.common.usecase.MyBillActionsHandler import com.navi.bbps.common.usecase.NaviBbpsConfigUseCase import com.navi.bbps.common.usecase.UploadUserDataUseCase import com.navi.bbps.common.utils.BillDetailsResponseToEntityMapper @@ -60,9 +58,7 @@ import com.navi.bbps.feature.category.model.view.PendingBillsShowMoreLessButtonS import com.navi.bbps.feature.category.model.view.RewardDataEntity import com.navi.bbps.feature.contactlist.PhoneContactManager import com.navi.bbps.feature.contactlist.model.view.PhoneContactEntity -import com.navi.bbps.feature.customerinput.model.network.BillDetailsRequest import com.navi.bbps.feature.customerinput.model.network.BillDetailsResponse -import com.navi.bbps.feature.customerinput.model.network.DeviceDetails import com.navi.bbps.feature.customerinput.model.view.BillerDetailsEntity import com.navi.bbps.feature.customerinput.model.view.CustomerParamsExtraData import com.navi.bbps.feature.destinations.CustomerDataInputScreenDestination @@ -133,6 +129,7 @@ constructor( val uploadUserDataUseCase: UploadUserDataUseCase, open val bbpsSharedPreferences: BbpsSharedPreferences, val findLastOrderWithSuccessfulPaymentUseCase: FindLastOrderWithSuccessfulPaymentUseCase, + private val myBillActionsHandler: MyBillActionsHandler, ) : NaviBbpsBaseVM( naviBbpsVmData = NaviBbpsVmData(screen = NaviBbpsScreen.NAVI_BBPS_BILL_CATEGORIES) @@ -639,20 +636,37 @@ constructor( fun deleteBill(myBillEntity: MyBillEntity) { viewModelScope.launch(dispatcherProvider.io) { - val deleteBillResponse = - bbpsCommonRepository.deleteBill( - myBillEntity.billId, - metricInfo = getBbpsMetricInfo(screenName = naviBbpsVmData.screen.screenName), - ) - if (deleteBillResponse.isSuccessWithData()) { - myBillsSyncJob.refreshBills(naviBbpsVmData.screen.screenName) - updateSnackBarState( - show = true, - messageId = R.string.bbps_account_removed_successfully, - ) - } else { - updateSnackBarState(show = true, messageId = R.string.bbps_account_delete_failed) - } + myBillActionsHandler.deleteBill( + billId = myBillEntity.billId, + categoryId = myBillEntity.categoryId, + screenName = naviBbpsVmData.screen.screenName, + onSuccess = { deleteBillResponse -> + naviBbpsAnalytics.billDeletionSuccess( + data = deleteBillResponse.data!!, + myBillEntity = myBillEntity, + sessionAttribute = getNaviBbpsSessionAttributes(), + source = source, + initialSource = initialSource, + ) + updateSnackBarState( + show = true, + messageId = R.string.bbps_account_removed_successfully, + ) + }, + onError = { deleteBillResponse -> + naviBbpsAnalytics.billDeletionFailed( + deleteBillResponse = deleteBillResponse, + myBillEntity = myBillEntity, + sessionAttribute = getNaviBbpsSessionAttributes(), + source = source, + initialSource = initialSource, + ) + updateSnackBarState( + show = true, + messageId = R.string.bbps_account_delete_failed, + ) + }, + ) } } @@ -818,27 +832,13 @@ constructor( 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()) { - myBillsSyncJob.refreshBills(naviBbpsVmData.screen.screenName) - updateSnackBarState(show = true, messageId = R.string.bbps_bill_marked_as_paid) - } + myBillActionsHandler.markAsPaid( + billId = myBillEntity.billId, + screenName = naviBbpsVmData.screen.screenName, + onSuccess = { + updateSnackBarState(show = true, messageId = R.string.bbps_bill_marked_as_paid) + }, + ) } } @@ -852,33 +852,27 @@ constructor( } updateRefreshState(isRefreshing = true, bill = myBillEntity) - val billDetailsRequest = - BillDetailsRequest( - billerId = myBillEntity.billerId, - customerParams = myBillEntity.customerParams, - deviceDetails = DeviceDetails(ip = naviNetworkConnectivity.getIpAddress()), - amount = myBillEntity.actualLastPaidAmount, - isConsentProvided = true, - ) - - val billDetailsResponse = - bbpsCommonRepository.fetchBillDetails( - billDetailsRequest = billDetailsRequest, - metricInfo = getBbpsMetricInfo(screenName = naviBbpsVmData.screen.screenName), - ) - - if (billDetailsResponse.isSuccessWithData()) { - myBillEntity.unpaidBillDetails?.let { - myBillsSyncJob.refreshBills(naviBbpsVmData.screen.screenName) + myBillActionsHandler.refreshBill( + myBillEntity = myBillEntity, + screenName = naviBbpsVmData.screen.screenName, + onSuccess = { updateSnackBarState( show = true, messageId = R.string.bbps_bill_refreshed_sucessfully, ) - } - } else { - updateSnackBarState(show = true, messageId = R.string.bbps_bill_refreshed_failed) - } - + }, + onError = { response -> + val errorCode = getError(response).code + if (errorCode in naviBbpsDefaultConfig.billAlreadyPaidErrorCodes) { + notifyError(response = response) + } else { + updateSnackBarState( + show = true, + messageId = R.string.bbps_bill_refreshed_failed, + ) + } + }, + ) updateRefreshState(isRefreshing = false, bill = myBillEntity) } diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/BillCategoryViewModelV2.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/BillCategoryViewModelV2.kt index aa7abcf6a1..32690a19fe 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/BillCategoryViewModelV2.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/BillCategoryViewModelV2.kt @@ -30,6 +30,7 @@ import com.navi.bbps.common.model.view.NaviPermissionResult import com.navi.bbps.common.repository.BbpsCommonRepository import com.navi.bbps.common.session.NaviBbpsSessionHelper import com.navi.bbps.common.usecase.FindLastOrderWithSuccessfulPaymentUseCase +import com.navi.bbps.common.usecase.MyBillActionsHandler import com.navi.bbps.common.usecase.NaviBbpsConfigUseCase import com.navi.bbps.common.usecase.OriginExperimentUtils import com.navi.bbps.common.usecase.UploadUserDataUseCase @@ -91,6 +92,7 @@ constructor( bbpsCommonRepository: BbpsCommonRepository, naviNetworkConnectivity: NaviNetworkConnectivity, uploadUserDataUseCase: UploadUserDataUseCase, + myBillActionsHandler: MyBillActionsHandler, @NaviBbpsGsonBuilder val naviBbpsGson: Gson, private val resourceProvider: ResourceProvider, val originBillDetectionHandler: OriginBillDetectionHandler, @@ -120,6 +122,7 @@ constructor( uploadUserDataUseCase = uploadUserDataUseCase, bbpsSharedPreferences = bbpsSharedPreferences, findLastOrderWithSuccessfulPaymentUseCase = findLastOrderWithSuccessfulPaymentUseCase, + myBillActionsHandler = myBillActionsHandler, ), BottomSheetController by BottomSheetControllerImpl() { diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/ui/BillCategoryBottomSheetContent.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/ui/BillCategoryBottomSheetContent.kt index 15f4ce9e7a..cf2931c3f7 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/ui/BillCategoryBottomSheetContent.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/ui/BillCategoryBottomSheetContent.kt @@ -12,12 +12,13 @@ import androidx.compose.ui.res.stringResource import com.navi.bbps.common.NaviBbpsAnalytics import com.navi.bbps.common.arc.ui.ArcNudgeBottomSheetContent import com.navi.bbps.common.ui.BbpsOfferBottomSheet +import com.navi.bbps.common.ui.BillOptionsBottomSheetContent import com.navi.bbps.common.ui.TitleDescriptionWithLinearProgressBar +import com.navi.bbps.common.utils.NaviBbpsCommonUtils.isRechargeCategory import com.navi.bbps.common.utils.getDisplayableAmount import com.navi.bbps.feature.category.model.view.BillCategoryBottomSheetType import com.navi.bbps.feature.mybills.ui.MarkAsPaidBottomSheetContent import com.navi.bbps.feature.mybills.ui.RemoveAccountBottomSheetContent -import com.navi.bbps.feature.mybills.ui.UnpaidBillOptionsBottomSheetContent import com.navi.bbps.getBillTitleFromAccountHolderNameOrPrimaryCustomerParams import com.navi.common.utils.firstLetterToLowerCase @@ -90,13 +91,15 @@ fun BillCategoryBottomSheetContent( ) } is BillCategoryBottomSheetType.UnpaidBillOptions -> { - UnpaidBillOptionsBottomSheetContent( + BillOptionsBottomSheetContent( billerLogoUrl = billCategoryBottomSheetType.myBillEntity.billerLogoUrl, title = getBillTitleFromAccountHolderNameOrPrimaryCustomerParams( - billCategoryBottomSheetType.myBillEntity.unpaidBillDetails - ?.accountHolderName, - billCategoryBottomSheetType.myBillEntity.primaryCustomerParamValue, + accountHolderName = + billCategoryBottomSheetType.myBillEntity.unpaidBillDetails + ?.accountHolderName, + primaryCustomerParams = + billCategoryBottomSheetType.myBillEntity.primaryCustomerParamValue, ), expiringOn = billCategoryBottomSheetType.myBillEntity.unpaidBillWarning @@ -106,7 +109,11 @@ fun BillCategoryBottomSheetContent( ?.amount ?.getDisplayableAmount() .orEmpty(), - operatorName = billCategoryBottomSheetType.myBillEntity.billerName, + billerName = billCategoryBottomSheetType.myBillEntity.billerName, + isRechargeCategory = + isRechargeCategory( + categoryId = billCategoryBottomSheetType.myBillEntity.categoryId + ), onMarkAsPaidClicked = { closeSheet() naviBbpsAnalytics.onMyBillMoreOptionsBottomSheetClicked( 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 a27f997fd3..c8b311ff2b 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 @@ -29,9 +29,11 @@ import com.navi.bbps.common.mapper.BillResponseToBillDetailsEntityMapper import com.navi.bbps.common.model.NaviBbpsVmData import com.navi.bbps.common.model.config.NaviBbpsDefaultConfig import com.navi.bbps.common.model.view.NaviPermissionResult +import com.navi.bbps.common.model.view.RefreshBillState import com.navi.bbps.common.repository.BbpsCommonRepository import com.navi.bbps.common.session.NaviBbpsSessionHelper import com.navi.bbps.common.usecase.FindLastOrderWithSuccessfulPaymentUseCase +import com.navi.bbps.common.usecase.MyBillActionsHandler import com.navi.bbps.common.usecase.NaviBbpsConfigUseCase import com.navi.bbps.common.usecase.RewardNudgeUseCase import com.navi.bbps.common.utils.BillDetailsResponseToEntityMapper @@ -50,9 +52,12 @@ import com.navi.bbps.feature.contactlist.model.view.PhoneContactEntity import com.navi.bbps.feature.customerinput.model.network.BillDetailsRequest import com.navi.bbps.feature.customerinput.model.network.DeviceDetails import com.navi.bbps.feature.customerinput.model.view.BillerDetailsEntity +import com.navi.bbps.feature.destinations.MyBillHistoryDetailsScreenDestination 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.network.PaymentAmountExactness import com.navi.bbps.feature.paybill.model.view.PayBillSource @@ -68,6 +73,7 @@ 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 @@ -76,7 +82,6 @@ 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 @@ -101,7 +106,10 @@ constructor( private val billDetailsResponseToEntityMapper: BillDetailsResponseToEntityMapper, private val resourceProvider: ResourceProvider, private val naviBbpsDateUtils: NaviBbpsDateUtils, - private val findLastOrderWithSuccessfulPaymentUseCase: FindLastOrderWithSuccessfulPaymentUseCase, + private val findLastOrderWithSuccessfulPaymentUseCase: + FindLastOrderWithSuccessfulPaymentUseCase, + private val myBillsRepository: MyBillsRepository, + private val myBillActionsHandler: MyBillActionsHandler, ) : NaviBbpsBaseVM( naviBbpsVmData = NaviBbpsVmData(screen = NaviBbpsScreen.NAVI_BBPS_CONTACT_LIST_SCREEN) @@ -137,6 +145,12 @@ constructor( private var operatorBillersList = emptyList() + private val _snackBarState = MutableStateFlow(SnackBarState(show = false)) + val snackBarState = _snackBarState.asStateFlow() + + private val _refreshBillItemAndStatus = MutableStateFlow(RefreshBillState()) + val refreshBillItemAndStatus = _refreshBillItemAndStatus.asStateFlow() + val isSearchQueryEmpty: StateFlow = _searchQuery .map { it.isEmpty() } @@ -156,9 +170,6 @@ constructor( private val _showPostpaidBottomSheet = MutableSharedFlow() val showPostpaidBottomSheet = _showPostpaidBottomSheet.asSharedFlow() - private val _showSnackBar = MutableStateFlow(false) - val showSnackBar = _showSnackBar.asStateFlow() - private val _allRecentBills = MutableStateFlow(RecentBillsEntity("", emptyList())) val allRecentBills = _allRecentBills.asStateFlow() @@ -166,11 +177,7 @@ constructor( val isContactListEmpty = _isContactListEmpty.asStateFlow() private val _contactListBottomSheetType = - MutableStateFlow( - ContactListBottomSheetType.MenuOptions( - ContactListBottomSheetType.createEmptyMyBillEntity() - ) - ) + MutableStateFlow(ContactListBottomSheetType.None) val contactListBottomSheetType = _contactListBottomSheetType.asStateFlow() var isPermissionPopupSeenOnLanded = false @@ -187,8 +194,12 @@ constructor( _contactListState.update { contactListState } } - fun updateSnackBarState(showSnackBar: Boolean) { - _showSnackBar.update { showSnackBar } + fun updateSnackBarState(show: Boolean, messageId: Int = R.string.bbps_copied_to_clipboard) { + _snackBarState.update { SnackBarState(show = show, messageId = messageId) } + } + + private fun updateRefreshState(isRefreshing: Boolean, bill: MyBillEntity? = null) { + _refreshBillItemAndStatus.value = RefreshBillState(isRefreshing, bill) } val phoneNumber = MutableStateFlow("") @@ -495,11 +506,20 @@ constructor( } } + private suspend fun getRecentBills(): RecentBillsEntity { + val savedBills = + bbpsCommonRepository.fetchSavedBillsByCategory(category = billCategoryEntity.categoryId) + + return RecentBillsEntity( + title = resourceProvider.getString(R.string.bbps_recent_recharges), + bills = savedBills, + ) + } + private fun observeRecentBills() { viewModelScope.launch(dispatcherProvider.io) { bbpsCommonRepository .fetchSavedBillsByCategoryAsFlow(categoryId = billCategoryEntity.categoryId) - .distinctUntilChanged() .collectLatest { recentBills -> if (contactListUIState.value is ContactListState.Loaded) { updateContactListUIState( @@ -519,33 +539,148 @@ constructor( } } - private suspend fun getRecentBills(): RecentBillsEntity { - val savedBills = - bbpsCommonRepository.fetchSavedBillsByCategory(category = billCategoryEntity.categoryId) - - return RecentBillsEntity( - title = resourceProvider.getString(R.string.bbps_recent_recharges), - bills = savedBills, - ) - } - - fun onDeleteMenuClicked(myBillEntity: MyBillEntity) { - _contactListBottomSheetType.update { ContactListBottomSheetType.MenuOptions(myBillEntity) } - } - - fun onDeleteAccountBillClicked(myBillEntity: MyBillEntity) { + fun onBillOptionsKebabMenuClicked(myBillEntity: MyBillEntity, openSheet: () -> Unit) { _contactListBottomSheetType.update { - ContactListBottomSheetType.Confirmation( - title = naviBbpsDefaultConfig.configMessage.removeAccountBottomSheetTitle, - description = naviBbpsDefaultConfig.configMessage.removeAccountBottomSheetMessage, - firstBtnTextResId = R.string.bbps_cancel, - secondButtonTextResId = R.string.bbps_remove, - onFirstBtnClick = {}, - onSecondBtnClick = { deleteBill(myBillEntity) }, + ContactListBottomSheetType.MenuOptions( + myBillEntity = myBillEntity, + onMarkAsPaidClicked = { + viewModelScope.launch { + delay(200) + /* this is added to make sure the bottom sheet is closed before + opening the new one and to make the transition smoother */ + _contactListBottomSheetType.update { + naviBbpsAnalytics.onBillOptionsClicked( + sessionAttribute = getNaviBbpsSessionAttributes(), + billEntity = myBillEntity, + action = resourceProvider.getString(R.string.bbps_mark_as_paid), + ) + ContactListBottomSheetType.MarkAsPaid( + myBillEntity = myBillEntity, + title = + resourceProvider.getString( + R.string.bbps_bill_mark_as_paid_heading + ), + description = + resourceProvider.getString( + R.string.bbps_bill_mark_as_paid_description + ), + firstBtnText = resourceProvider.getString(R.string.bbps_no), + secondButtonText = resourceProvider.getString(R.string.bbps_yes), + onFirstBtnClick = {}, + onSecondBtnClick = { + onMarkBillAsPaidConfirmedCtaClicked(myBillEntity = myBillEntity) + }, + ) + } + openSheet.invoke() + } + }, + onRefreshBillClicked = { + naviBbpsAnalytics.onBillOptionsClicked( + sessionAttribute = getNaviBbpsSessionAttributes(), + billEntity = myBillEntity, + action = resourceProvider.getString(R.string.bbps_refresh_bill), + ) + viewModelScope.launch { + if (!naviNetworkConnectivity.isInternetConnected()) { + notifyError(getNoInternetErrorConfig()) + return@launch + } else { + refreshBillAndUpdateInfo(myBillEntity = myBillEntity) + } + } + }, + onViewBillHistoryClicked = { + naviBbpsAnalytics.onBillOptionsClicked( + sessionAttribute = getNaviBbpsSessionAttributes(), + billEntity = myBillEntity, + action = resourceProvider.getString(R.string.bbps_view_bill_history), + ) + viewModelScope.launch { + delay(200) + _navigateToNextScreen.emit( + MyBillHistoryDetailsScreenDestination( + myBillEntity = myBillEntity, + source = naviBbpsVmData.screen.name, + initialSource = initialSource, + ) + ) + } + }, + onRemoveAccountClicked = { + naviBbpsAnalytics.onBillOptionsClicked( + sessionAttribute = getNaviBbpsSessionAttributes(), + billEntity = myBillEntity, + action = resourceProvider.getString(R.string.bbps_remove_account), + ) + viewModelScope.launch { + delay(200) + _contactListBottomSheetType.update { + ContactListBottomSheetType.RemoveAccount( + myBillEntity = myBillEntity, + title = + naviBbpsDefaultConfig.configMessage + .removeAccountBottomSheetTitle, + description = + naviBbpsDefaultConfig.configMessage + .removeAccountBottomSheetMessage, + firstBtnTextResId = R.string.bbps_cancel, + secondButtonTextResId = R.string.bbps_remove, + onFirstBtnClick = {}, + onSecondBtnClick = { deleteBill(myBillEntity) }, + ) + } + openSheet.invoke() + } + }, ) } } + 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 + } + myBillActionsHandler.markAsPaid( + billId = myBillEntity.billId, + screenName = naviBbpsVmData.screen.screenName, + onSuccess = { + updateSnackBarState(show = true, messageId = R.string.bbps_bill_marked_as_paid) + }, + ) + } + } + + private suspend fun refreshBillAndUpdateInfo(myBillEntity: MyBillEntity) { + updateRefreshState(isRefreshing = true, bill = myBillEntity) + myBillActionsHandler.refreshBill( + myBillEntity = myBillEntity, + screenName = naviBbpsVmData.screen.screenName, + onSuccess = { + updateSnackBarState( + show = true, + messageId = R.string.bbps_bill_refreshed_sucessfully, + ) + }, + onError = { response -> + val errorCode = getError(response).code + if (errorCode in naviBbpsDefaultConfig.billAlreadyPaidErrorCodes) { + notifyError(response = response) + } else { + updateSnackBarState( + show = true, + messageId = R.string.bbps_bill_refreshed_failed, + ) + } + }, + ) + updateRefreshState(isRefreshing = false, bill = myBillEntity) + } + fun updateInvalidContactListBottomSheetType() { _contactListBottomSheetType.update { ContactListBottomSheetType.InvalidContactNumberSelection( @@ -557,32 +692,38 @@ constructor( private fun deleteBill(myBillEntity: MyBillEntity) { viewModelScope.launch(Dispatchers.IO) { - val deleteBillResponse = - bbpsCommonRepository.deleteBill( - myBillEntity.billId, - metricInfo = getBbpsMetricInfo(screenName = naviBbpsVmData.screen.screenName), - ) - if (deleteBillResponse.isSuccessWithData()) { - naviBbpsAnalytics.billDeletedSuccessfully( - data = deleteBillResponse.data!!, - myBillEntity = myBillEntity, - sessionAttribute = getNaviBbpsSessionAttributes(), - source = source, - initialSource = initialSource, - ) - myBillsSyncJob.refreshBills(naviBbpsVmData.screen.screenName) - fetchContacts() - updateSnackBarState(true) - } else { - naviBbpsAnalytics.billDeletionFailed( - deleteBillResponse = deleteBillResponse, - myBillEntity = myBillEntity, - sessionAttribute = getNaviBbpsSessionAttributes(), - source = source, - initialSource = initialSource, - ) - notifyError(deleteBillResponse) - } + myBillActionsHandler.deleteBill( + billId = myBillEntity.billId, + categoryId = billCategoryEntity.categoryId, + screenName = naviBbpsVmData.screen.screenName, + onSuccess = { deleteBillResponse -> + naviBbpsAnalytics.billDeletionSuccess( + data = deleteBillResponse.data!!, + myBillEntity = myBillEntity, + sessionAttribute = getNaviBbpsSessionAttributes(), + source = source, + initialSource = initialSource, + ) + fetchContacts() + updateSnackBarState( + show = true, + messageId = R.string.bbps_account_removed_successfully, + ) + }, + onError = { deleteBillResponse -> + naviBbpsAnalytics.billDeletionFailed( + deleteBillResponse = deleteBillResponse, + myBillEntity = myBillEntity, + sessionAttribute = getNaviBbpsSessionAttributes(), + source = source, + initialSource = initialSource, + ) + updateSnackBarState( + show = true, + messageId = R.string.bbps_account_delete_failed, + ) + }, + ) } } diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/model/view/ContactListBottomSheetType.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/model/view/ContactListBottomSheetType.kt index 27577184c9..d9c7b4b99e 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/model/view/ContactListBottomSheetType.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/model/view/ContactListBottomSheetType.kt @@ -14,7 +14,33 @@ import com.navi.rr.common.models.CoinBurnData import com.navi.rr.common.models.OfferData sealed class ContactListBottomSheetType { - data class MenuOptions(val myBillEntity: MyBillEntity) : ContactListBottomSheetType() + data class MenuOptions( + val myBillEntity: MyBillEntity, + val onMarkAsPaidClicked: () -> Unit, + val onRefreshBillClicked: () -> Unit, + val onViewBillHistoryClicked: () -> Unit, + val onRemoveAccountClicked: () -> Unit, + ) : ContactListBottomSheetType() + + data class MarkAsPaid( + val myBillEntity: MyBillEntity, + val title: String, + val description: String? = null, + val firstBtnText: String, + val secondButtonText: String, + val onFirstBtnClick: () -> Unit, + val onSecondBtnClick: () -> Unit, + ) : ContactListBottomSheetType() + + data class RemoveAccount( + val myBillEntity: MyBillEntity, + val title: String, + val description: String? = null, + val firstBtnTextResId: Int, + val secondButtonTextResId: Int, + val onFirstBtnClick: () -> Unit, + val onSecondBtnClick: () -> Unit, + ) : ContactListBottomSheetType() data class Confirmation( val title: String, @@ -39,6 +65,8 @@ sealed class ContactListBottomSheetType { val phoneNumberDetailEntity: PhoneContactEntity, ) : ContactListBottomSheetType() + data object None : ContactListBottomSheetType() + companion object { fun createEmptyMyBillEntity(): MyBillEntity { return MyBillEntity( diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/ui/ContactListBottomSheetContent.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/ui/ContactListBottomSheetContent.kt index 8bd83754bf..f245ba4bca 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/ui/ContactListBottomSheetContent.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/ui/ContactListBottomSheetContent.kt @@ -7,9 +7,6 @@ package com.navi.bbps.feature.contactlist.ui -import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -19,7 +16,6 @@ import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material3.HorizontalDivider @@ -29,7 +25,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -40,14 +35,19 @@ import com.navi.bbps.common.NaviBbpsAnalytics import com.navi.bbps.common.theme.NaviBbpsColor import com.navi.bbps.common.ui.BbpsCircleImage import com.navi.bbps.common.ui.BbpsOfferBottomSheet +import com.navi.bbps.common.ui.BillOptionsBottomSheetContent import com.navi.bbps.common.ui.BottomSheetContentWithIconHeaderPrimarySecondaryButton import com.navi.bbps.common.ui.ConfirmationBottomSheetContent +import com.navi.bbps.common.utils.NaviBbpsCommonUtils.isRechargeCategory import com.navi.bbps.feature.billerlist.model.view.BillerItemEntity import com.navi.bbps.feature.contactlist.model.view.ContactListBottomSheetType import com.navi.bbps.feature.contactlist.model.view.PhoneContactEntity -import com.navi.bbps.feature.mybills.model.view.MyBillEntity +import com.navi.bbps.feature.mybills.ui.MarkAsPaidBottomSheetContent +import com.navi.bbps.feature.mybills.ui.RemoveAccountBottomSheetContent +import com.navi.bbps.getBillTitleFromAccountHolderNameOrPrimaryCustomerParams import com.navi.common.R as CommonR import com.navi.common.customview.LoaderRoundedButton +import com.navi.common.utils.firstLetterToLowerCase import com.navi.design.font.FontWeightEnum import com.navi.design.font.getFontWeight import com.navi.design.font.naviFontFamily @@ -57,7 +57,6 @@ import com.navi.naviwidgets.extensions.NaviText fun ContactListBottomSheetContent( contactListBottomSheetType: ContactListBottomSheetType, closeSheet: () -> Unit, - onDeleteAccountBillClicked: (MyBillEntity) -> Unit, naviBbpsAnalytics: NaviBbpsAnalytics.ContactList, sessionAttribute: Map, source: String, @@ -70,10 +69,46 @@ fun ContactListBottomSheetContent( when (contactListBottomSheetType) { is ContactListBottomSheetType.MenuOptions -> { - ContactListMenuBottomSheetContent( - onDeleteAccountBillClicked = { - onDeleteAccountBillClicked.invoke(contactListBottomSheetType.myBillEntity) - } + BillOptionsBottomSheetContent( + billerLogoUrl = contactListBottomSheetType.myBillEntity.billerLogoUrl, + title = + getBillTitleFromAccountHolderNameOrPrimaryCustomerParams( + accountHolderName = + contactListBottomSheetType.myBillEntity.unpaidBillDetails + ?.accountHolderName, + primaryCustomerParams = + contactListBottomSheetType.myBillEntity.primaryCustomerParamValue, + ), + expiringOn = + contactListBottomSheetType.myBillEntity.unpaidBillWarning + .firstLetterToLowerCase(), + isBillPaid = contactListBottomSheetType.myBillEntity.isBillPaid, + amount = + contactListBottomSheetType.myBillEntity.unpaidBillDetails?.amount.toString(), + billerName = contactListBottomSheetType.myBillEntity.billerName, + isRechargeCategory = + isRechargeCategory( + categoryId = contactListBottomSheetType.myBillEntity.categoryId + ), + onRemoveAccountClicked = { + closeSheet() + contactListBottomSheetType.onRemoveAccountClicked() + }, + onCloseClicked = { closeSheet() }, + onMarkAsPaidClicked = { + closeSheet() + if (!contactListBottomSheetType.myBillEntity.isBillPaid) { + contactListBottomSheetType.onMarkAsPaidClicked() + } + }, + onRefreshBillClicked = { + closeSheet() + contactListBottomSheetType.onRefreshBillClicked() + }, + onViewBillHistoryClicked = { + closeSheet() + contactListBottomSheetType.onViewBillHistoryClicked() + }, ) } is ContactListBottomSheetType.Confirmation -> { @@ -113,6 +148,40 @@ fun ContactListBottomSheetContent( closeSheet = closeSheet, ) } + is ContactListBottomSheetType.MarkAsPaid -> { + MarkAsPaidBottomSheetContent( + title = contactListBottomSheetType.title, + description = contactListBottomSheetType.description, + secondaryButtonText = contactListBottomSheetType.firstBtnText, + primaryButtonText = contactListBottomSheetType.secondButtonText, + onSecondaryButtonClicked = { + closeSheet() + contactListBottomSheetType.onFirstBtnClick() + }, + onPrimaryButtonClicked = { + closeSheet() + contactListBottomSheetType.onSecondBtnClick() + }, + ) + } + is ContactListBottomSheetType.RemoveAccount -> { + RemoveAccountBottomSheetContent( + title = contactListBottomSheetType.title, + description = contactListBottomSheetType.description, + secondaryButtonText = + stringResource(id = contactListBottomSheetType.firstBtnTextResId), + primaryButtonText = + stringResource(id = contactListBottomSheetType.secondButtonTextResId), + onSecondaryButtonClicked = { + closeSheet() + contactListBottomSheetType.onFirstBtnClick() + }, + onPrimaryButtonClicked = { + closeSheet() + contactListBottomSheetType.onSecondBtnClick() + }, + ) + } is ContactListBottomSheetType.InvalidContactNumberSelection -> { BottomSheetContentWithIconHeaderPrimarySecondaryButton( iconId = CommonR.drawable.ic_purple_exclamation, @@ -135,37 +204,8 @@ fun ContactListBottomSheetContent( onPostpaidOperatorSelectionContinueClicked, ) } - } -} - -@Composable -fun ContactListMenuBottomSheetContent(onDeleteAccountBillClicked: () -> Unit) { - Column( - modifier = - Modifier.fillMaxWidth() - .wrapContentHeight() - .padding(start = 16.dp, top = 16.dp, end = 16.dp, bottom = 32.dp) - ) { - Row( - modifier = - Modifier.fillMaxWidth().padding(vertical = 16.dp).clickable { - onDeleteAccountBillClicked() - }, - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically, - ) { - Image( - painter = painterResource(id = R.drawable.ic_remove_account), - contentDescription = null, - ) - Spacer(modifier = Modifier.width(14.dp)) - NaviText( - text = stringResource(id = R.string.bbps_remove_account), - fontSize = 14.sp, - fontFamily = naviFontFamily, - fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), - color = NaviBbpsColor.textPrimary, - ) + is ContactListBottomSheetType.None -> { + // Do nothing } } } diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/ui/ContactListScreen.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/ui/ContactListScreen.kt index 4595c0b2cd..176572ec24 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/ui/ContactListScreen.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/ui/ContactListScreen.kt @@ -61,9 +61,9 @@ import com.navi.bbps.feature.contactlist.model.view.ContactListState.Loaded import com.navi.bbps.feature.contactlist.model.view.ContactListState.Loading import com.navi.bbps.feature.contactlist.model.view.PhoneContactEntity import com.navi.bbps.feature.destinations.NaviBbpsPermissionScreenDestination -import com.navi.bbps.feature.mybills.model.view.MyBillEntity import com.navi.bbps.feature.permission.utils.PermissionKeys import com.navi.bbps.feature.permission.utils.PermissionUtils +import com.navi.design.snackbar.ErrorSnackBar import com.navi.design.snackbar.SuccessSnackBar import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.navigation.DestinationsNavigator @@ -101,7 +101,6 @@ fun ContactListScreen( val isSearchQueryEmpty by contactListViewModel.isSearchQueryEmpty.collectAsState(true) val contactListBottomSheetType by contactListViewModel.contactListBottomSheetType.collectAsStateWithLifecycle() - val showSnackBar by contactListViewModel.showSnackBar.collectAsStateWithLifecycle() val recentBills by contactListViewModel.allRecentBills.collectAsStateWithLifecycle() val bbpsSnackBarPredefinedConfig = remember { BbpsSnackBarPredefinedConfig() } val permissionResult by contactListViewModel.permissionResult.collectAsStateWithLifecycle() @@ -123,6 +122,9 @@ fun ContactListScreen( val context = LocalContext.current val view = LocalView.current val keyboardController = LocalSoftwareKeyboardController.current + val refreshBillItemAndStatus by + contactListViewModel.refreshBillItemAndStatus.collectAsStateWithLifecycle() + val snackBarState by contactListViewModel.snackBarState.collectAsStateWithLifecycle() LaunchedEffect(Unit) { contactListViewModel.fetchOffersAndCoinBurnForProduct( @@ -269,16 +271,6 @@ fun ContactListScreen( LaunchedEffect(Unit) { contactListViewModel.fetchContacts() } } - val onDeleteMenuClicked = { myBillEntity: MyBillEntity -> - contactListViewModel.onDeleteMenuClicked(myBillEntity) - openSheet() - } - - val onDeleteAccountBillClicked = { myBillEntity: MyBillEntity -> - contactListViewModel.onDeleteAccountBillClicked(myBillEntity) - openSheet() - } - val onContactSelected = { phoneNumberDetail: PhoneContactEntity -> naviBbpsAnalytics.onContactClick( billCategoryEntity = billCategoryEntity, @@ -345,7 +337,6 @@ fun ContactListScreen( ContactListBottomSheetContent( contactListBottomSheetType = contactListBottomSheetType, closeSheet = closeSheet, - onDeleteAccountBillClicked = onDeleteAccountBillClicked, naviBbpsAnalytics = naviBbpsAnalytics, sessionAttribute = contactListViewModel.getNaviBbpsSessionAttributes(), source = source, @@ -391,7 +382,7 @@ fun ContactListScreen( contactList = contactList, onContactSelected = onContactSelected, contactListState = contactListUIState as Loaded, - onDeleteRecentBillClicked = { + onBillOptionsKebabMenuClicked = { naviBbpsAnalytics.onDeleteAccountKebabMenuClicked( myBillEntity = it, sessionAttribute = @@ -399,7 +390,10 @@ fun ContactListScreen( source = source, initialSource = initialSource, ) - onDeleteMenuClicked(it) + contactListViewModel.onBillOptionsKebabMenuClicked( + myBillEntity = it, + openSheet = openSheet, + ) openSheet() }, onRecentBillItemClicked = @@ -433,6 +427,7 @@ fun ContactListScreen( openSheet() }, multipleOffersDataList = multipleOffersDataList, + refreshBillItemAndStatus = refreshBillItemAndStatus, onFailedOrderCalloutClicked = { val failedOrder = it.first val billDetails = it.second @@ -472,23 +467,41 @@ fun ContactListScreen( } }, snackbarHost = { - if (showSnackBar) { + if (snackBarState.show) { Column { - SuccessSnackBar( - modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), - show = true, - snackBarConfig = - bbpsSnackBarPredefinedConfig.successConfig( - title = - stringResource( - id = R.string.bbps_account_removed_successfully - ) - ), - onDismissed = { contactListViewModel.updateSnackBarState(false) }, - onTrailingIconClicked = { - contactListViewModel.updateSnackBarState(false) - }, - ) + if (snackBarState.messageId == R.string.bbps_bill_refreshed_failed) { + ErrorSnackBar( + modifier = + Modifier.fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 32.dp), + show = true, + snackBarConfig = + bbpsSnackBarPredefinedConfig.errorConfig( + title = stringResource(id = snackBarState.messageId) + ), + onDismissed = { + contactListViewModel.updateSnackBarState(show = false) + }, + onTrailingIconClicked = { + contactListViewModel.updateSnackBarState(show = false) + }, + ) + } else { + SuccessSnackBar( + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + show = true, + snackBarConfig = + bbpsSnackBarPredefinedConfig.successConfig( + title = stringResource(id = snackBarState.messageId) + ), + onDismissed = { + contactListViewModel.updateSnackBarState(show = false) + }, + onTrailingIconClicked = { + contactListViewModel.updateSnackBarState(show = false) + }, + ) + } Spacer(modifier = Modifier.height(16.dp)) } diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/ui/RenderContactListScreen.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/ui/RenderContactListScreen.kt index b7380f1dbc..76951989a5 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/ui/RenderContactListScreen.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/ui/RenderContactListScreen.kt @@ -50,13 +50,14 @@ import androidx.compose.ui.unit.sp import com.navi.bbps.R import com.navi.bbps.common.BBPS_CROSS_FADE_DURATION import com.navi.bbps.common.BILL_ITEM_SLIDE_OUT_DURATION -import com.navi.bbps.common.BULLET import com.navi.bbps.common.MOBILE_NUMBER_LENGTH import com.navi.bbps.common.NaviBbpsDimens import com.navi.bbps.common.model.view.NaviPermissionResult +import com.navi.bbps.common.model.view.RefreshBillState import com.navi.bbps.common.theme.NaviBbpsColor import com.navi.bbps.common.ui.BbpsCircleImage import com.navi.bbps.common.ui.BbpsListDivider +import com.navi.bbps.common.ui.BillAmountAndDueDateSectionWithShimmer import com.navi.bbps.common.ui.BillItemOfferWithShimmer import com.navi.bbps.common.ui.ContactIconView import com.navi.bbps.common.ui.FailedOrderCallout @@ -66,7 +67,6 @@ import com.navi.bbps.common.ui.OfferRolodexWithShimmer import com.navi.bbps.common.ui.PermissionTile import com.navi.bbps.common.ui.getOfferDataForMultipleBillItems import com.navi.bbps.common.utils.NaviBbpsCommonUtils.getNormalisedPhoneNumber -import com.navi.bbps.common.utils.getDisplayableAmount import com.navi.bbps.feature.contactlist.ContactListViewModel import com.navi.bbps.feature.contactlist.model.view.ContactListState import com.navi.bbps.feature.contactlist.model.view.PhoneContactEntity @@ -92,7 +92,7 @@ fun RenderContactListScreen( contactList: List, onContactSelected: (PhoneContactEntity) -> Unit, contactListState: ContactListState.Loaded, - onDeleteRecentBillClicked: (MyBillEntity) -> Unit, + onBillOptionsKebabMenuClicked: (MyBillEntity) -> Unit, onRecentBillItemClicked: (PhoneContactEntity, MyBillEntity) -> Unit, getContactNameFromPhoneNumber: (String) -> String, searchQuery: String, @@ -107,6 +107,7 @@ fun RenderContactListScreen( offerData: List?, coinBurnData: CoinBurnData?, multipleOffersDataList: Map>?, + refreshBillItemAndStatus: RefreshBillState, onFailedOrderCalloutClicked: (Pair) -> Unit, ) { val shouldShowRecentBills = @@ -166,6 +167,16 @@ fun RenderContactListScreen( } val vmRecentBills = rememberUpdatedState(contactListState.recentBills.bills) + fun updateAnimatedRecentBills() { + animatedRecentBills = vmRecentBills.value.toList() + } + + LaunchedEffect(vmRecentBills.value) { + if (animatedRecentBills.size == vmRecentBills.value.size) { + updateAnimatedRecentBills() + } + } + LazyColumn { if (isSearchQueryEmpty) { if (!isMyNumberItemVisible) { @@ -207,9 +218,10 @@ fun RenderContactListScreen( }, getContactNameFromPhoneNumber = getContactNameFromPhoneNumber, onRecentBillItemClicked = onRecentBillItemClicked, - onDeleteRecentBillClicked = onDeleteRecentBillClicked, + onBillOptionsKebabMenuClicked = onBillOptionsKebabMenuClicked, multipleOffersDataList = multipleOffersDataList, onFailedOrderCalloutClicked = onFailedOrderCalloutClicked, + refreshBillItemAndStatus = refreshBillItemAndStatus, ) } } @@ -276,9 +288,10 @@ fun RecentBillsSection( updateAnimatedRecentBills: () -> Unit, getContactNameFromPhoneNumber: (String) -> String, onRecentBillItemClicked: (PhoneContactEntity, MyBillEntity) -> Unit, - onDeleteRecentBillClicked: (MyBillEntity) -> Unit, + onBillOptionsKebabMenuClicked: (MyBillEntity) -> Unit, multipleOffersDataList: Map>?, onFailedOrderCalloutClicked: (Pair) -> Unit, + refreshBillItemAndStatus: RefreshBillState, ) { animatedRecentBills.forEachIndexed { index, billItem -> var failedOrder: OrderEntity? by remember { mutableStateOf(null) } @@ -315,7 +328,7 @@ fun RecentBillsSection( onRecentBillItemClicked = { onRecentBillItemClicked(phoneContactEntity, billItem) }, - onDeleteRecentBillClicked = { onDeleteRecentBillClicked(billItem) }, + onBillOptionsKebabMenuClicked = { onBillOptionsKebabMenuClicked(billItem) }, getContactNameFromPhoneNumber = getContactNameFromPhoneNumber, offerData = getOfferDataForMultipleBillItems( @@ -325,6 +338,7 @@ fun RecentBillsSection( primaryCustomerParamValue = billItem.primaryCustomerParamValue, failedOrder = failedOrder, onFailedOrderCalloutClicked = onFailedOrderCalloutClicked, + refreshBillItemAndStatus = refreshBillItemAndStatus, ) if (index < recentBills.size - 1) { BbpsListDivider( @@ -457,11 +471,12 @@ fun RecentBillItem( myBillEntity: MyBillEntity, onRecentBillItemClicked: () -> Unit, primaryCustomerParamValue: String, - onDeleteRecentBillClicked: (MyBillEntity) -> Unit, + onBillOptionsKebabMenuClicked: (MyBillEntity) -> Unit, getContactNameFromPhoneNumber: (String) -> String, offerData: OfferData?, failedOrder: OrderEntity? = null, onFailedOrderCalloutClicked: (Pair) -> Unit, + refreshBillItemAndStatus: RefreshBillState, ) { Box(modifier = modifier) { Column( @@ -500,87 +515,11 @@ fun RecentBillItem( ) Spacer(modifier = Modifier.height(2.dp)) - if (myBillEntity.isBillPaid) { - Row(verticalAlignment = Alignment.CenterVertically) { - if (myBillEntity.formattedLastPaidAmount.isNotBlank()) { - NaviText( - text = - stringResource( - id = R.string.bbps_rupee_symbol_x, - myBillEntity.formattedLastPaidAmount, - ), - color = NaviBbpsColor.textPrimary, - fontFamily = naviFontFamily, - fontSize = 14.sp, - fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD), - overflow = TextOverflow.Ellipsis, - ) - } - if (myBillEntity.formattedLastPaidDate.isNotBlank()) { - Spacer(modifier = Modifier.width(4.dp)) - NaviText( - text = BULLET, - color = NaviBbpsColor.textPrimary, - fontFamily = naviFontFamily, - fontSize = 16.sp, - fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), - ) - Spacer(modifier = Modifier.width(4.dp)) - NaviText( - text = - "${ - stringResource( - id = R.string.bbps_last_recharged, - myBillEntity.formattedLastPaidDate, - ) - } ", - color = NaviBbpsColor.textTertiary, - fontFamily = naviFontFamily, - fontSize = 12.sp, - fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), - overflow = TextOverflow.Ellipsis, - maxLines = 1, - ) - } - } - } else { - Row(verticalAlignment = Alignment.CenterVertically) { - if (myBillEntity.unpaidBillDetails?.amount?.isNotEmpty() == true) { - NaviText( - text = - stringResource( - id = R.string.bbps_rupee_symbol_x, - myBillEntity.unpaidBillDetails.amount - .getDisplayableAmount(), - ), - color = NaviBbpsColor.textPrimary, - fontFamily = naviFontFamily, - fontSize = 14.sp, - fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD), - overflow = TextOverflow.Ellipsis, - ) - Spacer(modifier = Modifier.width(4.dp)) - NaviText( - text = BULLET, - color = NaviBbpsColor.textPrimary, - fontFamily = naviFontFamily, - fontSize = 16.sp, - fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), - ) - Spacer(modifier = Modifier.width(4.dp)) - } - - NaviText( - text = myBillEntity.unpaidBillWarning, - color = NaviBbpsColor.onSurfaceCritical, - fontFamily = naviFontFamily, - fontSize = 12.sp, - fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), - overflow = TextOverflow.Ellipsis, - maxLines = 1, - ) - } - } + BillAmountAndDueDateSectionWithShimmer( + myBillEntity = myBillEntity, + refreshBillItemAndStatus = refreshBillItemAndStatus, + startWithPadding = false, + ) } Spacer(modifier = Modifier.width(16.dp)) @@ -620,7 +559,7 @@ fun RecentBillItem( Modifier.padding(top = 6.dp) .size(24.dp) .align(Alignment.Top) - .clickableDebounce { onDeleteRecentBillClicked(myBillEntity) }, + .clickableDebounce { onBillOptionsKebabMenuClicked(myBillEntity) }, ) } if (!myBillEntity.isBillPaid) { 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 66b08b58c3..5b4c230154 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 @@ -15,16 +15,13 @@ import com.navi.base.utils.EMPTY import com.navi.base.utils.NaviNetworkConnectivity import com.navi.base.utils.ResourceProvider import com.navi.base.utils.orZero -import com.navi.base.utils.retry import com.navi.bbps.R import com.navi.bbps.common.APP_VERSION_CODE import com.navi.bbps.common.BILLER_UNIQUE_ID import com.navi.bbps.common.CATEGORY_ID_MOBILE_PREPAID -import com.navi.bbps.common.DEFAULT_RETRY_COUNT import com.navi.bbps.common.NaviBbpsAnalytics import com.navi.bbps.common.NaviBbpsScreen import com.navi.bbps.common.RCBP_CATEGORY -import com.navi.bbps.common.RETRY_INTERVAL_IN_SECONDS import com.navi.bbps.common.TXN_AMOUNT import com.navi.bbps.common.model.NaviBbpsVmData import com.navi.bbps.common.model.config.NaviBbpsDefaultConfig @@ -32,9 +29,9 @@ import com.navi.bbps.common.model.view.DetectedBillEntity import com.navi.bbps.common.model.view.DetectedBillSource import com.navi.bbps.common.model.view.NaviPermissionResult import com.navi.bbps.common.model.view.RefreshBillState -import com.navi.bbps.common.repository.BbpsCommonRepository import com.navi.bbps.common.session.NaviBbpsSessionHelper import com.navi.bbps.common.usecase.FindLastOrderWithSuccessfulPaymentUseCase +import com.navi.bbps.common.usecase.MyBillActionsHandler import com.navi.bbps.common.usecase.OriginExperimentUtils import com.navi.bbps.common.usecase.UploadUserDataUseCase import com.navi.bbps.common.utils.BbpsOriginSessionHandler @@ -43,7 +40,6 @@ import com.navi.bbps.common.utils.MyBillEntityToBillCategoryEntityMapper import com.navi.bbps.common.utils.MyBillEntityToBillDetailsResponseMapper import com.navi.bbps.common.utils.MyBillEntityToBillerDetailsEntityMapper import com.navi.bbps.common.utils.NaviBbpsCommonUtils -import com.navi.bbps.common.utils.NaviBbpsCommonUtils.getBbpsMetricInfo import com.navi.bbps.common.utils.NaviBbpsCommonUtils.getNormalisedPhoneNumber import com.navi.bbps.common.utils.OriginSessionAttributes import com.navi.bbps.common.utils.OriginWidgetStatus @@ -54,9 +50,7 @@ import com.navi.bbps.feature.billerlist.model.view.BillerItemEntity import com.navi.bbps.feature.category.model.view.BillCategoryEntity import com.navi.bbps.feature.contactlist.PhoneContactManager import com.navi.bbps.feature.contactlist.model.view.PhoneContactEntity -import com.navi.bbps.feature.customerinput.model.network.BillDetailsRequest import com.navi.bbps.feature.customerinput.model.network.BillDetailsResponse -import com.navi.bbps.feature.customerinput.model.network.DeviceDetails import com.navi.bbps.feature.customerinput.model.view.BillerDetailsEntity import com.navi.bbps.feature.customerinput.model.view.CustomerParamsExtraData import com.navi.bbps.feature.destinations.CustomerDataInputScreenDestination @@ -75,7 +69,6 @@ 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 import com.navi.naviwidgets.utils.ZERO import com.navi.pay.tstore.list.model.view.OrderEntity import com.navi.rr.common.models.OfferRequest @@ -113,7 +106,6 @@ constructor( private val naviNetworkConnectivity: NaviNetworkConnectivity, private val phoneContactManager: Lazy, private val myBillsSyncJob: MyBillsSyncJob, - private val bbpsCommonRepository: BbpsCommonRepository, private val resourceProvider: ResourceProvider, val originBillDetectionHandler: OriginBillDetectionHandler, private val originSessionHandler: BbpsOriginSessionHandler, @@ -122,6 +114,7 @@ constructor( private val findLastOrderWithSuccessfulPaymentUseCase: FindLastOrderWithSuccessfulPaymentUseCase, private val originExperimentUtils: OriginExperimentUtils, + private val myBillActionsHandler: MyBillActionsHandler, ) : NaviBbpsBaseVM( naviBbpsVmData = NaviBbpsVmData(screen = NaviBbpsScreen.NAVI_BBPS_MY_SAVED_BILLS) @@ -417,32 +410,13 @@ constructor( 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()) { - - val updatedBillEntity = myBillEntity.copy(isBillPaid = true) - updateSavedBills( - updatedBillEntity = updatedBillEntity, - oldBillEntity = myBillEntity, - ) - updateSnackBarState(show = true, messageId = R.string.bbps_bill_marked_as_paid) - } + myBillActionsHandler.markAsPaid( + billId = myBillEntity.billId, + screenName = naviBbpsVmData.screen.screenName, + onSuccess = { + updateSnackBarState(show = true, messageId = R.string.bbps_bill_marked_as_paid) + }, + ) } } @@ -455,34 +429,27 @@ constructor( private suspend fun refreshBillAndUpdateInfo(myBillEntity: MyBillEntity) { updateRefreshState(isRefreshing = true, myBillEntity = myBillEntity) - - val billDetailsRequest = - BillDetailsRequest( - billerId = myBillEntity.billerId, - customerParams = myBillEntity.customerParams, - deviceDetails = DeviceDetails(ip = naviNetworkConnectivity.getIpAddress()), - amount = myBillEntity.actualLastPaidAmount, - isConsentProvided = true, - ) - - val billDetailsResponse = - bbpsCommonRepository.fetchBillDetails( - billDetailsRequest = billDetailsRequest, - metricInfo = getBbpsMetricInfo(screenName = naviBbpsVmData.screen.screenName), - ) - - if (billDetailsResponse.isSuccessWithData() && billDetailsResponse.data != null) { - myBillEntity.unpaidBillDetails?.let { - myBillsSyncJob.refreshBills(naviBbpsVmData.screen.screenName) + myBillActionsHandler.refreshBill( + myBillEntity = myBillEntity, + screenName = naviBbpsVmData.screen.screenName, + onSuccess = { updateSnackBarState( show = true, messageId = R.string.bbps_bill_refreshed_sucessfully, ) - } - } else { - updateSnackBarState(show = true, messageId = R.string.bbps_bill_refreshed_failed) - } - + }, + onError = { response -> + val errorCode = getError(response).code + if (errorCode in naviBbpsDefaultConfig.billAlreadyPaidErrorCodes) { + notifyError(response = response) + } else { + updateSnackBarState( + show = true, + messageId = R.string.bbps_bill_refreshed_failed, + ) + } + }, + ) updateRefreshState(isRefreshing = false, myBillEntity = myBillEntity) } @@ -765,20 +732,37 @@ constructor( fun deleteBill(myBillEntity: MyBillEntity) { viewModelScope.launch(dispatcherProvider.io) { - val deleteBillResponse = - bbpsCommonRepository.deleteBill( - myBillEntity.billId, - metricInfo = getBbpsMetricInfo(screenName = naviBbpsVmData.screen.screenName), - ) - if (deleteBillResponse.isSuccessWithData()) { - myBillsSyncJob.refreshBills(naviBbpsVmData.screen.screenName) - updateSnackBarState( - show = true, - messageId = R.string.bbps_account_removed_successfully, - ) - } else { - updateSnackBarState(show = true, messageId = R.string.bbps_account_delete_failed) - } + myBillActionsHandler.deleteBill( + billId = myBillEntity.billId, + categoryId = myBillEntity.categoryId, + screenName = naviBbpsVmData.screen.screenName, + onSuccess = { deleteBillResponse -> + naviBbpsAnalytics.billDeletionSuccess( + data = deleteBillResponse.data!!, + myBillEntity = myBillEntity, + sessionAttribute = getNaviBbpsSessionAttributes(), + source = source, + initialSource = initialSource, + ) + updateSnackBarState( + show = true, + messageId = R.string.bbps_account_removed_successfully, + ) + }, + onError = { deleteBillResponse -> + naviBbpsAnalytics.billDeletionFailed( + deleteBillResponse = deleteBillResponse, + myBillEntity = myBillEntity, + sessionAttribute = getNaviBbpsSessionAttributes(), + source = source, + initialSource = initialSource, + ) + updateSnackBarState( + show = true, + messageId = R.string.bbps_account_delete_failed, + ) + }, + ) } } diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/ui/MyBillsBottomSheetContent.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/ui/MyBillsBottomSheetContent.kt index 2c7b79930a..7bbcb09e8f 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/ui/MyBillsBottomSheetContent.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/ui/MyBillsBottomSheetContent.kt @@ -7,57 +7,21 @@ package com.navi.bbps.feature.mybills.ui -import androidx.annotation.StringRes -import androidx.compose.foundation.Image -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.navigationBarsPadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable -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.SpanStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.withStyle -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import coil.compose.AsyncImage -import com.navi.bbps.R -import com.navi.bbps.common.BULLET -import com.navi.bbps.common.ICON_MARK_AS_PAID_UNPAID_BILL_BOTTOMSHEET -import com.navi.bbps.common.ICON_REFRESH_BILL_UNPAID_BILL_BOTTOMSHEET -import com.navi.bbps.common.ICON_REMOVE_ACCOUNT_UNPAID_BILL_BOTTOMSHEET -import com.navi.bbps.common.ICON_VIEW_BILL_HISTORY_UNPAID_BILL_BOTTOMSHEET import com.navi.bbps.common.NaviBbpsAnalytics -import com.navi.bbps.common.SYMBOL_RUPEE -import com.navi.bbps.common.theme.NaviBbpsColor -import com.navi.bbps.common.ui.BbpsAsyncImage import com.navi.bbps.common.ui.BbpsOfferBottomSheet +import com.navi.bbps.common.ui.BillOptionsBottomSheetContent import com.navi.bbps.common.ui.ConfirmationBottomSheetContent import com.navi.bbps.common.ui.TitleDescriptionWithLinearProgressBar +import com.navi.bbps.common.utils.NaviBbpsCommonUtils.isRechargeCategory import com.navi.bbps.common.utils.getDisplayableAmount import com.navi.bbps.feature.category.ui.DetectedBillsBottomSheetContent import com.navi.bbps.feature.category.ui.NoBillDetectedErrorBottomSheetContent import com.navi.bbps.feature.mybills.model.view.MyBillsBottomSheetType import com.navi.bbps.getBillTitleFromAccountHolderNameOrPrimaryCustomerParams -import com.navi.bbps.noRippleClickableWithDebounce import com.navi.common.R as CommonR -import com.navi.common.utils.SPACE import com.navi.common.utils.firstLetterToLowerCase -import com.navi.design.font.FontWeightEnum -import com.navi.design.font.getFontWeight -import com.navi.design.font.naviFontFamily -import com.navi.guarddog.utils.clickableDebounce -import com.navi.naviwidgets.extensions.NaviText @Composable fun MyBillsBottomSheetContent( @@ -69,12 +33,15 @@ fun MyBillsBottomSheetContent( ) { when (myBillsBottomSheetType) { is MyBillsBottomSheetType.UnpaidBillOptions -> { - UnpaidBillOptionsBottomSheetContent( + BillOptionsBottomSheetContent( billerLogoUrl = myBillsBottomSheetType.myBillEntity.billerLogoUrl, title = getBillTitleFromAccountHolderNameOrPrimaryCustomerParams( - myBillsBottomSheetType.myBillEntity.unpaidBillDetails?.accountHolderName, - myBillsBottomSheetType.myBillEntity.primaryCustomerParamValue, + accountHolderName = + myBillsBottomSheetType.myBillEntity.unpaidBillDetails + ?.accountHolderName, + primaryCustomerParams = + myBillsBottomSheetType.myBillEntity.primaryCustomerParamValue, ), expiringOn = (myBillsBottomSheetType.myBillEntity.unpaidBillWarning) @@ -84,7 +51,9 @@ fun MyBillsBottomSheetContent( ?.amount ?.getDisplayableAmount() .orEmpty(), - operatorName = myBillsBottomSheetType.myBillEntity.billerName, + billerName = myBillsBottomSheetType.myBillEntity.billerName, + isRechargeCategory = + isRechargeCategory(categoryId = myBillsBottomSheetType.myBillEntity.categoryId), onMarkAsPaidClicked = { closeSheet() naviBbpsAnalytics.onMyBillMoreOptionsBottomSheetClicked( @@ -105,7 +74,14 @@ fun MyBillsBottomSheetContent( closeSheet() naviBbpsAnalytics.onMyBillMoreOptionsBottomSheetClicked( billEntity = myBillsBottomSheetType.myBillEntity, - action = "view_bill_history", + action = + if ( + isRechargeCategory( + categoryId = myBillsBottomSheetType.myBillEntity.categoryId + ) + ) + "view_recharge_history" + else "view_bill_history", ) myBillsBottomSheetType.onViewBillHistoryClicked() }, @@ -254,170 +230,3 @@ fun RemoveAccountBottomSheetContent( onPrimaryButtonClicked = { onPrimaryButtonClicked.invoke() }, ) } - -@Composable -fun UnpaidBillOptionsBottomSheetContent( - billerLogoUrl: String, - title: String, - amount: String, - operatorName: String, - expiringOn: String, - onMarkAsPaidClicked: () -> Unit, - onRefreshBillClicked: () -> Unit, - onViewBillHistoryClicked: () -> Unit, - onRemoveAccountClicked: () -> Unit, - onCloseClicked: () -> Unit, -) { - val descriptionText = buildAnnotatedString { - val fullText = stringResource(id = R.string.bbps_payment_due, operatorName, expiringOn) - withStyle( - style = - SpanStyle( - fontFamily = naviFontFamily, - fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), - fontSize = 14.sp, - color = NaviBbpsColor.textTertiary, - ) - ) { - append(fullText) - } - addStyle( - style = - SpanStyle( - fontFamily = naviFontFamily, - fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), - fontSize = 14.sp, - color = NaviBbpsColor.inputFieldError, - ), - start = fullText.indexOf(expiringOn), - end = fullText.indexOf(expiringOn) + expiringOn.length, - ) - } - - Column( - modifier = - Modifier.navigationBarsPadding() - .fillMaxWidth() - .wrapContentHeight() - .padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 32.dp) - ) { - Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { - BbpsAsyncImage( - imageUrl = billerLogoUrl, - modifier = Modifier.size(24.dp), - placeholderIconResId = com.navi.common.R.drawable.navi_common_ic_biller_placeholder, - ) - Spacer(modifier = Modifier.weight(1f)) - Image( - painter = painterResource(id = com.navi.common.R.drawable.ic_close_black), - contentDescription = null, - modifier = Modifier.size(24.dp).clickableDebounce { onCloseClicked() }, - ) - } - Spacer(modifier = Modifier.height(8.dp)) - NaviText( - text = - buildString { - if (title.isNotBlank()) { - append(title) - append(SPACE) - } - if (title.isNotBlank() && amount.isNotBlank()) { - append(BULLET) - append(SPACE) - } - if (amount.isNotBlank()) { - append(SYMBOL_RUPEE) - append(amount) - } - }, - fontSize = 16.sp, - fontFamily = naviFontFamily, - fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD), - color = NaviBbpsColor.textPrimary, - ) - Spacer(modifier = Modifier.height(2.dp)) - - if (descriptionText.isNotBlank()) { - NaviText( - text = descriptionText, - fontSize = 14.sp, - fontFamily = naviFontFamily, - fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), - color = NaviBbpsColor.textTertiary, - lineHeight = 22.sp, - ) - } - - Spacer(modifier = Modifier.height(24.dp)) - - val actions = - listOf( - ActionItem( - imageS3Url = ICON_MARK_AS_PAID_UNPAID_BILL_BOTTOMSHEET, - textResId = R.string.bbps_mark_as_paid, - onClick = onMarkAsPaidClicked, - ), - ActionItem( - imageS3Url = ICON_REFRESH_BILL_UNPAID_BILL_BOTTOMSHEET, - textResId = R.string.bbps_refresh_bill, - onClick = onRefreshBillClicked, - ), - ActionItem( - imageS3Url = ICON_VIEW_BILL_HISTORY_UNPAID_BILL_BOTTOMSHEET, - textResId = R.string.bbps_view_bill_history, - onClick = onViewBillHistoryClicked, - ), - ActionItem( - imageS3Url = ICON_REMOVE_ACCOUNT_UNPAID_BILL_BOTTOMSHEET, - textResId = R.string.bbps_remove_account, - onClick = onRemoveAccountClicked, - ), - ) - - actions.forEachIndexed { index, action -> - IconActionRow( - imageS3Url = action.imageS3Url, - text = stringResource(id = action.textResId), - onClick = action.onClick, - ) - - if (index < actions.lastIndex) { - HorizontalDivider( - modifier = Modifier.padding(vertical = 20.dp), - thickness = 1.dp, - color = NaviBbpsColor.borderAlt, - ) - } - } - } -} - -data class ActionItem( - val imageS3Url: String, - @StringRes val textResId: Int, - val onClick: () -> Unit, -) - -@Composable -fun IconActionRow( - imageS3Url: String, - text: String, - onClick: () -> Unit, - modifier: Modifier = Modifier, -) { - Row( - modifier = modifier.fillMaxWidth().noRippleClickableWithDebounce(onClick = onClick), - verticalAlignment = Alignment.CenterVertically, - ) { - AsyncImage(modifier = Modifier.size(24.dp), model = imageS3Url, contentDescription = "") - Spacer(modifier = Modifier.width(12.dp)) - NaviText( - text = text, - fontSize = 14.sp, - fontFamily = naviFontFamily, - fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), - color = NaviBbpsColor.textPrimary, - ) - } -} diff --git a/android/navi-bbps/src/main/res/values/strings.xml b/android/navi-bbps/src/main/res/values/strings.xml index cbf74ca3b7..ff36d068c6 100644 --- a/android/navi-bbps/src/main/res/values/strings.xml +++ b/android/navi-bbps/src/main/res/values/strings.xml @@ -270,6 +270,7 @@ %s payment %s. due on %s Refresh bill + View recharge history View bill history %s Bill payment offer %s Bill payment offers