From d87ae3cb71fc6e51c6c9561cf106a257c2323064 Mon Sep 17 00:00:00 2001 From: A Shrihari Raju Date: Fri, 13 Dec 2024 22:30:04 +0530 Subject: [PATCH] NTP-9071 || Shrihari | New SIP Experience (#13694) --- .../dashboard/DashboardBaseActivity.kt | 164 +- .../dashboard/viewmodels/DashboardSharedVM.kt | 17 + .../processHandlerImpl/HopperHelper.kt | 36 + .../processHandlerImpl/ViewModelMapper.kt | 7 + .../home/compose/activity/HomePageActivity.kt | 1 + .../home/navigation/HomeContentNavHost.kt | 5 +- .../navigation/NavGraphNavingationItem.kt | 8 +- .../home/ui/screen/HomeContentFrame.kt | 5 +- .../home/ui/screen/HomeContentFrameRoot.kt | 5 +- .../common/PaymentCard.kt | 4 +- .../repo/InvestmentsTabV2Repository.kt | 13 +- .../investmentTab/InvestmentsScreen.kt | 12 +- .../investmentTab/InvestmentsScreenHelper.kt | 320 ++- .../compose/investmentTab/InvestmentsTab.kt | 48 +- .../PaymentCardComposable.kt | 6 + .../SipAutoPayCardComposable.kt | 56 +- .../dashboard/viewmodels/InvestmentsVm.kt | 63 + .../home/model/HpBottomSheetStateHolder.kt | 15 + .../bottomsheet/ui/HomeScreenBottomSheet.kt | 18 + .../amc/common/activity/CheckerActivity.kt | 98 +- .../amc/common/fragment/AmcBaseFragment.kt | 84 + .../common/fragment/AmcCommonBottomSheet.kt | 19 +- .../AmcCommonComposableBottomSheet.kt | 78 + .../common/fragment/AmcDynamicBottomSheet.kt | 263 ++ .../common/fragment/OrderStatusFragment.kt | 163 +- .../navi/amc/common/fragment/OtpFragment.kt | 170 +- .../model/AdditionalDataAsyncResponse.kt | 2 + .../common/model/AmcCommonBottomSheetData.kt | 8 +- .../amc/common/model/InformationCardData.kt | 4 +- .../amc/common/model/OrderStatusScreenData.kt | 1 + .../navi/amc/common/model/PaymentRequest.kt | 1 + .../amc/common/model/SipOrderSummaryData.kt | 25 + .../navi/amc/common/repo/CheckerRepository.kt | 12 +- .../com/navi/amc/common/repo/OTPRepository.kt | 10 + .../com/navi/amc/common/view/FooterView.kt | 35 +- .../navi/amc/common/viewmodel/CheckerVM.kt | 14 +- .../com/navi/amc/common/viewmodel/OTPVM.kt | 25 + .../fragments/AutoPaySuccessFragment.kt | 12 + .../fundbuy/fragments/FundBuyingFragmentV2.kt | 3 + .../fundbuy/fragments/FundBuyingFragmentV3.kt | 2185 +++++++++++++++++ .../fundbuy/models/AutoPaySetupRequestData.kt | 9 +- .../amc/fundbuy/models/FundBuyScreenData.kt | 42 +- .../navi/amc/fundbuy/models/SipDetailsData.kt | 1 + .../fundbuy/repository/FundBuyRepository.kt | 39 + .../fundbuy/viewmodel/FundBuyFlowViewModel.kt | 66 +- .../fundbuy/viewmodel/FundBuyV2ViewModel.kt | 59 +- .../amc/fundbuy/views/AmountChipGroupView.kt | 71 +- .../amc/network/retrofit/RetrofitService.kt | 26 +- .../fragments/ModifySipDetailsFragment.kt | 157 +- .../amc/portfolio/fragments/SipFragment.kt | 134 +- .../portfolio/fragments/SipModifyFragment.kt | 153 +- .../repositories/SipDetailsRepository.kt | 16 +- .../amc/portfolio/viewmodels/SipViewModel.kt | 22 + .../amc/portfolio/views/AutoPayCardView.kt | 33 + .../java/com/navi/amc/utils/AmcAnalytics.kt | 2 + .../java/com/navi/amc/utils/ColorUtils.kt | 1 + .../main/java/com/navi/amc/utils/Constant.kt | 16 + .../src/main/java/com/navi/amc/utils/Ext.kt | 8 + .../com/navi/amc/utils/SubPageStatusType.kt | 4 + .../res/layout/amc_common_bottomsheet.xml | 46 +- .../amc_common_composable_bottomsheet.xml | 18 + .../res/layout/amount_chip_group_view.xml | 7 +- .../src/main/res/layout/autopay_card.xml | 14 + .../src/main/res/layout/footer_btn.xml | 14 + .../main/res/layout/fragment_auto_success.xml | 54 +- .../res/layout/fund_buying_fragment_v3.xml | 399 +++ .../main/res/layout/order_status_layout.xml | 47 +- .../main/res/layout/payment_btn_layout.xml | 19 +- .../navi-amc/src/main/res/values/colors.xml | 2 +- .../navi/common/model/AmcBottomSheetData.kt | 65 + .../compose/GenericComposableBottomSheet.kt | 395 +++ .../ui/fragment/SingleSelectionBottomSheet.kt | 106 +- .../java/com/navi/common/utils/Constants.kt | 4 + .../common/viewmodel/GenericBottomSheetVM.kt | 53 + .../res/drawable/ic_radio_button_default.xml | 0 .../res/drawable/ic_radio_button_selected.xml | 0 .../layout/single_selection_bottom_sheet.xml | 61 +- .../main/res/layout/single_selection_item.xml | 12 +- .../navi/gold/ui/DigitalGoldHomeActivity.kt | 11 +- .../navi/naviwidgets/base/InputWidgetData.kt | 1 + .../navi/naviwidgets/models/LineItemData.kt | 5 +- .../models/SingleSelectionBottomSheetData.kt | 9 +- .../java/com/navi/naviwidgets/utils/Utils.kt | 21 + .../InputSearchWidgetMeta.kt | 7 +- .../ui/LabeledTextSearchInputWidget.kt | 37 +- 85 files changed, 5993 insertions(+), 288 deletions(-) create mode 100644 android/navi-amc/src/main/java/com/navi/amc/common/fragment/AmcCommonComposableBottomSheet.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/common/fragment/AmcDynamicBottomSheet.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/common/model/SipOrderSummaryData.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/fundbuy/fragments/FundBuyingFragmentV3.kt create mode 100644 android/navi-amc/src/main/res/layout/amc_common_composable_bottomsheet.xml create mode 100644 android/navi-amc/src/main/res/layout/fund_buying_fragment_v3.xml create mode 100644 android/navi-common/src/main/java/com/navi/common/model/AmcBottomSheetData.kt create mode 100644 android/navi-common/src/main/java/com/navi/common/ui/compose/GenericComposableBottomSheet.kt create mode 100644 android/navi-common/src/main/java/com/navi/common/viewmodel/GenericBottomSheetVM.kt rename android/{navi-amc => navi-common}/src/main/res/drawable/ic_radio_button_default.xml (100%) rename android/{navi-amc => navi-common}/src/main/res/drawable/ic_radio_button_selected.xml (100%) diff --git a/android/app/src/main/java/com/naviapp/dashboard/DashboardBaseActivity.kt b/android/app/src/main/java/com/naviapp/dashboard/DashboardBaseActivity.kt index 3acd0e9ddd..4fb2ad8d4d 100644 --- a/android/app/src/main/java/com/naviapp/dashboard/DashboardBaseActivity.kt +++ b/android/app/src/main/java/com/naviapp/dashboard/DashboardBaseActivity.kt @@ -26,6 +26,7 @@ import com.navi.common.utils.CommonNaviAnalytics import com.navi.common.utils.Constants.UNDERSCORE import com.navi.common.utils.getNetworkType import com.navi.common.utils.log +import com.navi.payment.model.clientmodels.PostPaymentData import com.navi.payment.paymenthandler.factories.PaymentProviderFactory import com.navi.payment.paymenthandler.model.PaymentRequest import com.navi.payment.paymenthandler.model.PaymentStatusData @@ -132,63 +133,124 @@ abstract class DashboardBaseActivity : } override fun onPaymentError(code: Int, description: String?, data: PaymentData?) { - NaviTrackEvent.trackEvent( - eventName = - "${ModuleNameV2.PL.name.lowercase()}$UNDERSCORE${PaymentAnalytics.PAYMENT_SDK_EXIT}", - eventValues = - mapOf( - PaymentAnalytics.STATUS to false.toString(), - PaymentAnalytics.PROVIDER to provider.toString(), - PaymentAnalytics.PAYMENT_ID to data?.paymentId.orEmpty(), - PaymentAnalytics.SYNC to paymentSyncFlowStatus.toString() - ) - ) - val statusData = - PaymentStatusData( - code = code, - orderId = data?.orderId, - paymentReferenceId = data?.paymentId, - description = description, - status = FAILURE, - provider = ProviderType.RAZORPAY + if (dashboardSharedVM.isAmcPayment == true) { + // using this flag to check if module is AMC or PL to be removed in future + NaviTrackEvent.trackEvent( + eventName = + "${ModuleNameV2.AMC.name.lowercase()}$UNDERSCORE${PaymentAnalytics.PAYMENT_SDK_EXIT}", + eventValues = + mapOf( + PaymentAnalytics.STATUS to false.toString(), + PaymentAnalytics.PROVIDER to provider.toString(), + PaymentAnalytics.PAYMENT_ID to data?.paymentId.orEmpty(), + PaymentAnalytics.SYNC to paymentSyncFlowStatus.toString() + ) ) - dashboardSharedVM.setPgRepaymentResponseStatus(statusData) + val statusData = + PostPaymentData( + code = code.toString(), + orderId = data?.orderId, + paymentOrderReferenceId = data?.paymentId, + description = description, + provider = provider.toString(), + status = FAILURE + ) + dashboardSharedVM.setPostPaymentResponseStatus(statusData) + val analyticsTracker = CommonNaviAnalytics.naviAnalytics.Errors() + analyticsTracker.onAppDowntime( + reason = description, + screenName = screenName, + moduleName = ModuleName.AMC.name, + statusCode = code, + networkType = getNetworkType(this), + flowName = "payment", + methodName = "onPaymentError", + vendorName = "Razorpay" + ) + } else { + NaviTrackEvent.trackEvent( + eventName = + "${ModuleNameV2.PL.name.lowercase()}$UNDERSCORE${PaymentAnalytics.PAYMENT_SDK_EXIT}", + eventValues = + mapOf( + PaymentAnalytics.STATUS to false.toString(), + PaymentAnalytics.PROVIDER to provider.toString(), + PaymentAnalytics.PAYMENT_ID to data?.paymentId.orEmpty(), + PaymentAnalytics.SYNC to paymentSyncFlowStatus.toString() + ) + ) + val statusData = + PaymentStatusData( + code = code, + orderId = data?.orderId, + paymentReferenceId = data?.paymentId, + description = description, + status = FAILURE, + provider = ProviderType.RAZORPAY + ) + dashboardSharedVM.setPgRepaymentResponseStatus(statusData) - val analyticsTracker = CommonNaviAnalytics.naviAnalytics.Errors() - analyticsTracker.onAppDowntime( - reason = description, - screenName = screenName, - moduleName = ModuleName.LE.name, - statusCode = code, - networkType = getNetworkType(this), - flowName = "payment", - methodName = "onPaymentError", - vendorName = "Razorpay" - ) + val analyticsTracker = CommonNaviAnalytics.naviAnalytics.Errors() + analyticsTracker.onAppDowntime( + reason = description, + screenName = screenName, + moduleName = ModuleName.LE.name, + statusCode = code, + networkType = getNetworkType(this), + flowName = "payment", + methodName = "onPaymentError", + vendorName = "Razorpay" + ) + } } override fun onPaymentSuccess(description: String?, data: PaymentData?) { - NaviTrackEvent.trackEvent( - eventName = - "${ModuleNameV2.PL.name.lowercase()}$UNDERSCORE${PaymentAnalytics.PAYMENT_SDK_EXIT}", - eventValues = - mapOf( - PaymentAnalytics.STATUS to true.toString(), - PaymentAnalytics.PROVIDER to provider.toString(), - PaymentAnalytics.PAYMENT_ID to data?.paymentId.orEmpty(), - PaymentAnalytics.SYNC to paymentSyncFlowStatus.toString() - ) - ) - val statusData = - PaymentStatusData( - code = 200, - orderId = data?.orderId, - paymentReferenceId = data?.paymentId, - description = description, - status = SUCCESS, - provider = ProviderType.RAZORPAY + if (dashboardSharedVM.isAmcPayment == true) { + // using this flag to check if module is AMC or PL to be removed in future + NaviTrackEvent.trackEvent( + eventName = + "${ModuleNameV2.AMC.name.lowercase()}$UNDERSCORE${PaymentAnalytics.PAYMENT_SDK_EXIT}", + eventValues = + mapOf( + PaymentAnalytics.STATUS to true.toString(), + PaymentAnalytics.PROVIDER to provider.toString(), + PaymentAnalytics.PAYMENT_ID to data?.paymentId.orEmpty(), + PaymentAnalytics.SYNC to paymentSyncFlowStatus.toString() + ) ) - dashboardSharedVM.setPgRepaymentResponseStatus(statusData) + val statusData = + PostPaymentData( + code = "200", + orderId = data?.orderId, + paymentOrderReferenceId = data?.paymentId, + description = description, + provider = provider.toString(), + status = SUCCESS + ) + dashboardSharedVM.setPostPaymentResponseStatus(statusData) + } else { + NaviTrackEvent.trackEvent( + eventName = + "${ModuleNameV2.PL.name.lowercase()}$UNDERSCORE${PaymentAnalytics.PAYMENT_SDK_EXIT}", + eventValues = + mapOf( + PaymentAnalytics.STATUS to true.toString(), + PaymentAnalytics.PROVIDER to provider.toString(), + PaymentAnalytics.PAYMENT_ID to data?.paymentId.orEmpty(), + PaymentAnalytics.SYNC to paymentSyncFlowStatus.toString() + ) + ) + val statusData = + PaymentStatusData( + code = 200, + orderId = data?.orderId, + paymentReferenceId = data?.paymentId, + description = description, + status = SUCCESS, + provider = ProviderType.RAZORPAY + ) + dashboardSharedVM.setPgRepaymentResponseStatus(statusData) + } } override fun onPaymentVerify(orderID: String?) { diff --git a/android/app/src/main/java/com/naviapp/dashboard/viewmodels/DashboardSharedVM.kt b/android/app/src/main/java/com/naviapp/dashboard/viewmodels/DashboardSharedVM.kt index 14f77a9d86..7d7d30d45b 100644 --- a/android/app/src/main/java/com/naviapp/dashboard/viewmodels/DashboardSharedVM.kt +++ b/android/app/src/main/java/com/naviapp/dashboard/viewmodels/DashboardSharedVM.kt @@ -10,9 +10,12 @@ package com.naviapp.dashboard.viewmodels import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope +import com.navi.amc.common.model.AdditionalDataAsyncResponse +import com.navi.amc.common.model.NextCtaResponse import com.navi.base.model.ActionData import com.navi.common.model.GroupFaq import com.navi.common.viewmodel.BaseVM +import com.navi.payment.model.clientmodels.PostPaymentData import com.navi.payment.paymenthandler.model.PaymentStatusData import com.naviapp.dashboard.repositories.DashboardRepository import com.naviapp.models.GiPaymentError @@ -20,6 +23,7 @@ import com.naviapp.models.response.CustomerSupportOptionsResponse import com.naviapp.network.ApiErrorTagType import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch class DashboardSharedVM(private val repository: DashboardRepository = DashboardRepository()) : @@ -53,6 +57,19 @@ class DashboardSharedVM(private val repository: DashboardRepository = DashboardR val hideHelpBottomSheet = MutableLiveData(false) + private val _postPaymentStatus = MutableSharedFlow(replay = 1) + val postPaymentStatus = _postPaymentStatus.asSharedFlow().distinctUntilChanged() + + var isAmcPayment: Boolean? = null + var isSyncFlow = false + var syncFlowPaymentInitStartTime = 0L + var paymentInitiateData: AdditionalDataAsyncResponse? = null + var paymentFlowType: String? = null + + fun setPostPaymentResponseStatus(data: PostPaymentData?) { + viewModelScope.safeLaunch { _postPaymentStatus.emit(data) } + } + fun fetchFaqs(product: String?, subProduct: String?, naeScreenName: String) { coroutineScope.launch { val response = repository.fetchFaqs(product, subProduct, naeScreenName) diff --git a/android/app/src/main/java/com/naviapp/home/common/hopperProcessor/processHandlerImpl/HopperHelper.kt b/android/app/src/main/java/com/naviapp/home/common/hopperProcessor/processHandlerImpl/HopperHelper.kt index 0336a58b25..fc2440df69 100644 --- a/android/app/src/main/java/com/naviapp/home/common/hopperProcessor/processHandlerImpl/HopperHelper.kt +++ b/android/app/src/main/java/com/naviapp/home/common/hopperProcessor/processHandlerImpl/HopperHelper.kt @@ -12,6 +12,8 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.lifecycleScope import com.navi.amc.common.viewmodel.CheckerVM +import com.navi.amc.fundbuy.models.AutoPaySetupRequestData +import com.navi.amc.fundbuy.viewmodel.FundBuyFlowViewModel import com.navi.amc.fundbuy.viewmodel.FundListViewModel import com.navi.amc.kyc.viewmodel.BankDetailsVM import com.navi.amc.kyc.viewmodel.EmploymentDetailsVM @@ -25,7 +27,10 @@ import com.navi.amc.kyc.viewmodel.SearchBankVM import com.navi.amc.kyc.viewmodel.SignatureVM import com.navi.amc.portfolio.viewmodels.SipModificationVM import com.navi.amc.utils.Constant.ACTION_PERFORMED +import com.navi.amc.utils.Constant.AMOUNT +import com.navi.amc.utils.Constant.BANK_DETAILS_REF_ID import com.navi.amc.utils.Constant.MANDATE_OPTED_IN +import com.navi.amc.utils.Constant.PAYMENT_MODE import com.navi.amc.utils.Constant.SIP_REFERENCE_ID import com.navi.analytics.utils.NaviTrackEvent import com.navi.base.model.CtaData @@ -38,6 +43,7 @@ import com.navi.common.utils.Constants.HOPPER_PROCESS_FETCH_AND_CACHE_DATA_START import com.navi.common.utils.TemporaryStorageHelper import com.navi.insurance.util.observeNullable import com.naviapp.home.dashboard.viewmodels.InvestmentsVm +import com.naviapp.utils.Constants.INVESTMENT_TAB_SCREEN_V3 import com.naviapp.utils.Constants.KYC_JOURNEY import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -136,6 +142,36 @@ class HopperHelper { } } } + is FundBuyFlowViewModel -> { + ctaData.parameters?.let { params -> + val amount = params.firstOrNull { it.key == AMOUNT }?.value.orEmpty() + val paymentMode = + params.firstOrNull { it.key == PAYMENT_MODE }?.value.orEmpty() + val bankDetailsRefId = + params.firstOrNull { it.key == BANK_DETAILS_REF_ID }?.value + val sipReferenceId = + params.firstOrNull { it.key == SIP_REFERENCE_ID }?.value + + val autoPaySetupRequestData = + AutoPaySetupRequestData( + bankAccountRefId = bankDetailsRefId, + mandateType = paymentMode, + amount = amount, + sipReferenceId = sipReferenceId + ) + + viewModel.postAutoPaySetupRequestDataV2( + autoPaySetupRequestData, + screenName = INVESTMENT_TAB_SCREEN_V3 + ) + observeAndHandleResponse( + activity, + ctaData, + viewModel.autoPayPaymentInitiateData, + onResult + ) + } + } else -> { onResult(false) } diff --git a/android/app/src/main/java/com/naviapp/home/common/hopperProcessor/processHandlerImpl/ViewModelMapper.kt b/android/app/src/main/java/com/naviapp/home/common/hopperProcessor/processHandlerImpl/ViewModelMapper.kt index 57a657e43f..023c7782e2 100644 --- a/android/app/src/main/java/com/naviapp/home/common/hopperProcessor/processHandlerImpl/ViewModelMapper.kt +++ b/android/app/src/main/java/com/naviapp/home/common/hopperProcessor/processHandlerImpl/ViewModelMapper.kt @@ -11,6 +11,8 @@ import androidx.activity.ComponentActivity import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.navi.amc.common.viewmodel.CheckerVM +import com.navi.amc.common.viewmodel.OTPVM +import com.navi.amc.fundbuy.viewmodel.FundBuyFlowViewModel import com.navi.amc.fundbuy.viewmodel.FundListViewModel import com.navi.amc.kyc.viewmodel.BankDetailsVM import com.navi.amc.kyc.viewmodel.EmploymentDetailsVM @@ -25,6 +27,8 @@ import com.navi.amc.kyc.viewmodel.SearchBankVM import com.navi.amc.kyc.viewmodel.SignatureVM import com.navi.amc.portfolio.viewmodels.SipModificationVM import com.navi.base.model.CtaData +import com.navi.common.utils.Constants.AMC_FUND_AUTOPAY_SETUP_V3 +import com.navi.common.utils.Constants.AMC_FUND_OTP import com.navi.common.utils.Constants.AMC_HPC_NAME_REDIRECT_PAGE_URL import com.navi.common.utils.Constants.AMC_HPC_PAN_REDIRECT_PAGE_URL import com.navi.common.utils.Constants.AMC_KYC_BANK_DETAILS_URL @@ -65,6 +69,9 @@ class ViewModelMapper { AMC_HPC_NAME_REDIRECT_PAGE_URL -> ViewModelProvider(activity)[CheckerVM::class.java] AMC_SIP_MODIFY_URL -> ViewModelProvider(activity)[SipModificationVM::class.java] HOPPER -> ViewModelProvider(activity)[InvestmentsVm::class.java] + AMC_FUND_OTP -> ViewModelProvider(activity)[OTPVM::class.java] + AMC_FUND_AUTOPAY_SETUP_V3 -> + ViewModelProvider(activity)[FundBuyFlowViewModel::class.java] else -> null } } diff --git a/android/app/src/main/java/com/naviapp/home/compose/activity/HomePageActivity.kt b/android/app/src/main/java/com/naviapp/home/compose/activity/HomePageActivity.kt index 00c5f96b45..a9e6ab67ff 100644 --- a/android/app/src/main/java/com/naviapp/home/compose/activity/HomePageActivity.kt +++ b/android/app/src/main/java/com/naviapp/home/compose/activity/HomePageActivity.kt @@ -315,6 +315,7 @@ class HomePageActivity : notificationVM = notificationVM, sharedVM = sharedVM, paymentVM = paymentVM, + paymentManager = paymentManager, screenOverlayVM = screenOverlayVM, analytics = homeScreenAnalytics, onTabSelected = { onBottomTabSelected(it) }, diff --git a/android/app/src/main/java/com/naviapp/home/compose/home/navigation/HomeContentNavHost.kt b/android/app/src/main/java/com/naviapp/home/compose/home/navigation/HomeContentNavHost.kt index 66844e8144..d76811a502 100644 --- a/android/app/src/main/java/com/naviapp/home/compose/home/navigation/HomeContentNavHost.kt +++ b/android/app/src/main/java/com/naviapp/home/compose/home/navigation/HomeContentNavHost.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable +import com.navi.paymentclients.viewmodel.base.PaymentManager import com.naviapp.analytics.utils.NaviAnalytics import com.naviapp.common.viewmodel.BottomNavBarVM import com.naviapp.dashboard.viewmodels.DashboardSharedVM @@ -40,6 +41,7 @@ fun HomeContentNavHost( screenOverlayVM: ScreenOverlayVM, hopper: Hopper, bottomNavBarVM: BottomNavBarVM, + paymentManager: PaymentManager, nudgeState: () -> NudgeState ) { NavHost( @@ -63,7 +65,8 @@ fun HomeContentNavHost( hopper = hopper, homeVM = homeViewModel, bottomNavBarVM = bottomNavBarVM, - nudgeState = nudgeState + nudgeState = nudgeState, + paymentManager = paymentManager ) } } diff --git a/android/app/src/main/java/com/naviapp/home/compose/home/navigation/NavGraphNavingationItem.kt b/android/app/src/main/java/com/naviapp/home/compose/home/navigation/NavGraphNavingationItem.kt index 1ec8b6d7ba..7505ba6fbd 100644 --- a/android/app/src/main/java/com/naviapp/home/compose/home/navigation/NavGraphNavingationItem.kt +++ b/android/app/src/main/java/com/naviapp/home/compose/home/navigation/NavGraphNavingationItem.kt @@ -17,6 +17,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp +import com.navi.paymentclients.viewmodel.base.PaymentManager import com.naviapp.analytics.utils.NaviAnalytics import com.naviapp.common.viewmodel.BottomNavBarVM import com.naviapp.dashboard.viewmodels.DashboardSharedVM @@ -45,6 +46,7 @@ fun NavGraphNavigationItem( hopper: Hopper, bottomNavBarVM: BottomNavBarVM, nudgeState: () -> NudgeState, + paymentManager: PaymentManager ) { when (tabId) { NavigationItem.Home.tabId -> @@ -57,7 +59,7 @@ fun NavGraphNavigationItem( naviAnalyticsEventTracker = naviHomeAnalytics, screenOverlayVM = screenOverlayVM, homeVM = homeVM, - nudgeState = nudgeState, + nudgeState = nudgeState ) NavigationItem.Investment.tabId -> InvestmentsScreen( @@ -69,7 +71,9 @@ fun NavGraphNavigationItem( .background(Color.White), sharedVM = sharedVM, hopper = hopper, - bottomNavBarVM = bottomNavBarVM + bottomNavBarVM = bottomNavBarVM, + paymentManager = paymentManager, + dashboardSharedVM = dashboardSharedVM, ) NavigationItem.Loan.tabId -> LoansTabScreen( diff --git a/android/app/src/main/java/com/naviapp/home/compose/home/ui/screen/HomeContentFrame.kt b/android/app/src/main/java/com/naviapp/home/compose/home/ui/screen/HomeContentFrame.kt index 70baeb42b3..16d714a004 100644 --- a/android/app/src/main/java/com/naviapp/home/compose/home/ui/screen/HomeContentFrame.kt +++ b/android/app/src/main/java/com/naviapp/home/compose/home/ui/screen/HomeContentFrame.kt @@ -18,6 +18,7 @@ import androidx.navigation.NavHostController import com.navi.common.ui.compose.NaviDrawerValue import com.navi.common.ui.compose.NaviModalNavigationDrawer import com.navi.common.ui.compose.rememberNaviDrawerState +import com.navi.paymentclients.viewmodel.base.PaymentManager import com.naviapp.analytics.utils.NaviAnalytics import com.naviapp.common.viewmodel.BottomNavBarVM import com.naviapp.dashboard.viewmodels.DashboardSharedVM @@ -62,7 +63,8 @@ fun HomeContentFrame( sharedVM: SharedVM, navController: NavHostController, hopper: Hopper, - onHomeScreenEvent: (event: HpEvents) -> Unit = {} + onHomeScreenEvent: (event: HpEvents) -> Unit = {}, + paymentManager: PaymentManager, ) { InitScreenOverlayComponents(screenOverlayVM, sharedVM, selectedTabId, hpStates, activity) val nudgeState by screenOverlayVM.nudgeState.collectAsStateWithLifecycle() @@ -121,6 +123,7 @@ fun HomeContentFrame( screenOverlayVM = screenOverlayVM, hopper = hopper, bottomNavBarVM = bottomNavBarVM, + paymentManager = paymentManager, nudgeState = { nudgeState } ) }, diff --git a/android/app/src/main/java/com/naviapp/home/compose/home/ui/screen/HomeContentFrameRoot.kt b/android/app/src/main/java/com/naviapp/home/compose/home/ui/screen/HomeContentFrameRoot.kt index bcd74b744e..d49936d86e 100644 --- a/android/app/src/main/java/com/naviapp/home/compose/home/ui/screen/HomeContentFrameRoot.kt +++ b/android/app/src/main/java/com/naviapp/home/compose/home/ui/screen/HomeContentFrameRoot.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.compose.rememberNavController +import com.navi.paymentclients.viewmodel.base.PaymentManager import com.naviapp.analytics.utils.NaviAnalytics import com.naviapp.common.viewmodel.BottomNavBarVM import com.naviapp.dashboard.viewmodels.DashboardSharedVM @@ -36,6 +37,7 @@ fun HomePageContentFrameRoot( profileVM: ProfileVM, homeVM: HomeViewModel, paymentVM: PaymentVM, + paymentManager: PaymentManager, screenOverlayVM: ScreenOverlayVM, notificationVM: NotificationVM, sharedVM: SharedVM, @@ -92,6 +94,7 @@ fun HomePageContentFrameRoot( navController = navController, hopper = hopper, screenOverlayVM = screenOverlayVM, - onHomeScreenEvent = { homeVM.sendEvent(it) } + onHomeScreenEvent = { homeVM.sendEvent(it) }, + paymentManager = paymentManager, ) } diff --git a/android/app/src/main/java/com/naviapp/home/dashboard/models/investmentTabWidgetData/common/PaymentCard.kt b/android/app/src/main/java/com/naviapp/home/dashboard/models/investmentTabWidgetData/common/PaymentCard.kt index c1773a24ee..4d556f20f6 100644 --- a/android/app/src/main/java/com/naviapp/home/dashboard/models/investmentTabWidgetData/common/PaymentCard.kt +++ b/android/app/src/main/java/com/naviapp/home/dashboard/models/investmentTabWidgetData/common/PaymentCard.kt @@ -7,6 +7,7 @@ import com.google.gson.annotations.SerializedName import com.navi.base.model.ActionData +import com.navi.common.model.GenericBottomSheetData import com.navi.naviwidgets.models.response.ImageFieldData import com.navi.naviwidgets.models.response.TextFieldData import com.naviapp.home.dashboard.models.investmentTabWidgetData.common.InvestmentBaseProperty @@ -20,7 +21,8 @@ data class PaymentCard( @SerializedName("actionData") val actionData: ActionData? = null, @SerializedName("investmentsIcon") val investmentsIcon: ImageFieldData? = null, @SerializedName("properties") val properties: CardProperties? = null, - @SerializedName("bottomSheetData") val bottomSheetData: BottomSheetData? = null + @SerializedName("bottomSheetData") val bottomSheetData: BottomSheetData? = null, + @SerializedName("multiBottomSheetData") val multiBottomSheetData: GenericBottomSheetData? = null ) data class PaymentCardHeader( diff --git a/android/app/src/main/java/com/naviapp/home/dashboard/repo/InvestmentsTabV2Repository.kt b/android/app/src/main/java/com/naviapp/home/dashboard/repo/InvestmentsTabV2Repository.kt index d12c381e95..3a4e219fa6 100644 --- a/android/app/src/main/java/com/naviapp/home/dashboard/repo/InvestmentsTabV2Repository.kt +++ b/android/app/src/main/java/com/naviapp/home/dashboard/repo/InvestmentsTabV2Repository.kt @@ -29,12 +29,13 @@ constructor(@SuperAppRetroFit private val superAppRetrofitService: RetrofitServi metricInfo: MetricInfo> ): RepoResult { return apiResponseCallback( - superAppRetrofitService.fetchInvestmentTabScreenResponse( - acceptEncoding = GZIP, - target = ModuleNameV2.ALCHEMIST.name, - verticalType = ModuleNameV2.ALCHEMIST.name, - screenId = screenId - ), + response = + superAppRetrofitService.fetchInvestmentTabScreenResponse( + acceptEncoding = GZIP, + target = ModuleNameV2.ALCHEMIST.name, + verticalType = ModuleNameV2.ALCHEMIST.name, + screenId = screenId + ), metricInfo = metricInfo ) } diff --git a/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/InvestmentsScreen.kt b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/InvestmentsScreen.kt index 34fcf85b34..0767c08b47 100644 --- a/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/InvestmentsScreen.kt +++ b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/InvestmentsScreen.kt @@ -16,7 +16,9 @@ import com.navi.common.model.ModuleNameV2 import com.navi.common.ui.errorview.FullScreenErrorComposeView import com.navi.common.utils.setStatusBarColorInt import com.navi.design.utils.parseColorSafe +import com.navi.paymentclients.viewmodel.base.PaymentManager import com.naviapp.common.viewmodel.BottomNavBarVM +import com.naviapp.dashboard.viewmodels.DashboardSharedVM import com.naviapp.home.common.hopperProcessor.processHandlerImpl.Hopper import com.naviapp.home.compose.activity.HomePageActivity import com.naviapp.home.dashboard.ui.compose.investmentTab.InvestmentsScreenHelper @@ -33,7 +35,9 @@ fun InvestmentsScreen( modifier: Modifier = Modifier, sharedVM: SharedVM, hopper: Hopper, - bottomNavBarVM: BottomNavBarVM + bottomNavBarVM: BottomNavBarVM, + paymentManager: PaymentManager, + dashboardSharedVM: DashboardSharedVM ) { val investmentsTabVm by lazy { ViewModelProvider(activity)[InvestmentsVm::class.java] } val investmentsScreenData = @@ -67,10 +71,12 @@ fun InvestmentsScreen( activity = activity, investmentsTabVm = investmentsTabVm, sharedVM = sharedVM, + investmentsScreenHelper = investmentsScreenHelper, hopper = hopper, toggleStatusBarColor = { toggleStatusBarColor(activity, it) }, - investmentsScreenHelper = investmentsScreenHelper, - bottomNavBarVM = bottomNavBarVM + bottomNavBarVM = bottomNavBarVM, + paymentManager = paymentManager, + dashboardSharedVM = dashboardSharedVM ) investmentsTabVm.recordScreenRenderTime( INVESTMENT_TAB_SCREEN_V3, diff --git a/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/InvestmentsScreenHelper.kt b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/InvestmentsScreenHelper.kt index 97e3605fcb..c9befe7df0 100644 --- a/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/InvestmentsScreenHelper.kt +++ b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/InvestmentsScreenHelper.kt @@ -11,25 +11,45 @@ import BottomSheetData import android.app.Activity import android.os.Bundle import androidx.compose.runtime.MutableState +import com.navi.amc.common.model.AdditionalDataAsyncResponse +import com.navi.amc.common.model.NextCtaResponse import com.navi.amc.navigator.NaviAmcDeeplinkNavigator +import com.navi.amc.utils.AmcAnalytics import com.navi.amc.utils.Constant import com.navi.amc.utils.Constant.CAPS_DATA +import com.navi.amc.utils.Constant.CHANGE_PAYMENT_MODE +import com.navi.amc.utils.Constant.DISMISS +import com.navi.amc.utils.Constant.NET_BANKING +import com.navi.amc.utils.Constant.PAYMENT_MODE_CHANGED import com.navi.amc.utils.Constant.SHOW_BOTTOMSHEET +import com.navi.amc.utils.Constant.UPI import com.navi.amc.utils.TempStorageHelper import com.navi.analytics.utils.NaviTrackEvent import com.navi.base.deeplink.DeepLinkManager import com.navi.base.model.ActionData import com.navi.base.model.CtaData import com.navi.base.model.GenericAnalytics +import com.navi.base.utils.isNotNullAndNotEmpty import com.navi.base.utils.isNull import com.navi.base.utils.orFalse +import com.navi.common.model.AmcBottomSheetData +import com.navi.common.model.GenericBottomSheetData import com.navi.common.model.common.InvestmentTabNudgeData +import com.navi.common.utils.Constants.AMC_FUND_AUTOPAY_SETUP_V3 +import com.navi.common.utils.Constants.GET_MULTI_BOTTOMSHEET import com.navi.common.utils.Constants.HOPPER +import com.navi.common.utils.TemporaryStorageHelper +import com.navi.common.utils.toActionData import com.navi.common.utils.toCtaData import com.navi.naviwidgets.models.FooterButtonState +import com.navi.payment.model.initiatesdk.PaymentPrefetchMethodRequest +import com.navi.payment.utils.PaymentAnalytics +import com.navi.paymentclients.viewmodel.base.PaymentManager +import com.naviapp.dashboard.viewmodels.DashboardSharedVM import com.naviapp.home.common.hopperProcessor.processHandlerImpl.Hopper import com.naviapp.home.compose.activity.HomePageActivity import com.naviapp.home.dashboard.viewmodels.InvestmentsVm +import com.naviapp.home.model.AmcGenericBottomSheetData import com.naviapp.home.model.HpBottomSheetComposableType import com.naviapp.home.model.HpBottomSheetContent import com.naviapp.home.model.HpBottomSheetRenderType @@ -84,7 +104,10 @@ class InvestmentsScreenHelper { sharedVM: SharedVM, bottomSheetData: BottomSheetData? = null, index: Int = 0, - investmentsTabVm: InvestmentsVm + investmentsTabVm: InvestmentsVm, + multiBottomSheetData: GenericBottomSheetData? = null, + paymentManager: PaymentManager, + dashboardSharedVM: DashboardSharedVM ) { actionData?.metaData?.clickedData?.apply { if (parameters == null) { @@ -109,11 +132,25 @@ class InvestmentsScreenHelper { onSuccess = { bottomSheetActionData, bundle -> bottomSheetActionData?.let { actionData -> handlePennyDropSuccessCase(actionData.toCtaData()) - navigateTo( - ctaData = actionData.toCtaData(), - activity = activity, - bundle = bundle - ) + if (actionData.url == GET_MULTI_BOTTOMSHEET) { + handleAction( + actionData, + activity, + sharedVM, + bottomSheetData, + index, + investmentsTabVm, + multiBottomSheetData, + paymentManager, + dashboardSharedVM + ) + } else { + navigateTo( + ctaData = actionData.toCtaData(), + activity = activity, + bundle = bundle + ) + } } }, onHopperStart = { ctaData, buttonState, bottomSheetData -> @@ -122,33 +159,48 @@ class InvestmentsScreenHelper { buttonState?.value = FooterButtonState.ENABLED } ctaData?.let { - activity.hopper.startProcess( - activity = activity, - ctaData = it, - onResult = { ctaData -> - buttonState?.value = FooterButtonState.ENABLED - sharedVM.updateBottomSheetState( - state = HpBottomSheetState.Hidden - ) - when (investmentsTabVm.bottomSheetType) { - AUTOPAY_NUDGE_BOTTOMSHEET -> { - handleAutoPayNudgeBottomSheetData( - bottomSheetData, - ctaData, - activity - ) - } - else -> { - ctaData?.let { ctaData -> - navigateTo( - ctaData = ctaData, - activity = activity + if (ctaData.url == GET_MULTI_BOTTOMSHEET) { + handleAction( + ctaData.toActionData(), + activity, + sharedVM, + bottomSheetData, + index, + investmentsTabVm, + multiBottomSheetData, + paymentManager, + dashboardSharedVM + ) + } else { + activity.hopper.startProcess( + activity = activity, + ctaData = it, + onResult = { ctaData -> + buttonState?.value = + FooterButtonState.ENABLED + sharedVM.updateBottomSheetState( + state = HpBottomSheetState.Hidden + ) + when (investmentsTabVm.bottomSheetType) { + AUTOPAY_NUDGE_BOTTOMSHEET -> { + handleAutoPayNudgeBottomSheetData( + bottomSheetData, + ctaData, + activity ) } + else -> { + ctaData?.let { ctaData -> + navigateTo( + ctaData = ctaData, + activity = activity + ) + } + } } } - } - ) + ) + } } } ), @@ -164,6 +216,28 @@ class InvestmentsScreenHelper { ) } } + GET_MULTI_BOTTOMSHEET -> { + val action = multiBottomSheetData?.actionData + val bottomSheets = multiBottomSheetData?.bottomSheetsData + handleMultiBottomSheetAction( + bottomSheets, + action, + activity, + sharedVM, + investmentsTabVm, + listener = { + initializePaymentActivity( + ctaData = it.toCtaData(), + paymentManager = paymentManager, + activity = activity, + dashboardSharedVM = dashboardSharedVM, + investmentsTabVm = investmentsTabVm + ) + }, + paymentManager = paymentManager, + dashboardSharedVM = dashboardSharedVM + ) + } else -> { handlePennyDropSuccessCase(actionData.toCtaData()) navigateTo(ctaData = actionData.toCtaData(), activity = activity) @@ -228,4 +302,192 @@ class InvestmentsScreenHelper { } } } + + private fun handleMultiBottomSheetAction( + multiBottomSheetData: List?, + action: ActionData?, + activity: HomePageActivity, + sharedVM: SharedVM, + investmentsTabVm: InvestmentsVm, + listener: ((ActionData) -> Unit)? = null, + paymentManager: PaymentManager, + dashboardSharedVM: DashboardSharedVM + ): Any? { + action?.let { + when (action.url) { + SHOW_BOTTOMSHEET -> { + if (action.type in listOf(UPI, NET_BANKING, CHANGE_PAYMENT_MODE)) { + val bottomSheet = multiBottomSheetData?.firstOrNull { it.id == action.type } + bottomSheet?.let { it -> + sharedVM.updateBottomSheetState( + config = + AmcGenericBottomSheetData( + onSuccess = { action, bundle -> + if (action != null) { + handleMultiBottomSheetAction( + multiBottomSheetData, + action, + activity, + sharedVM, + investmentsTabVm, + listener, + paymentManager = paymentManager, + dashboardSharedVM = dashboardSharedVM + ) + ?.let { it1 -> + listener?.invoke(it1 as ActionData) + } + } + }, + onHopperStart = { + ctaData, + buttonState, + bottomSheetData, + selectedItem -> + if (ctaData.isNull()) { + activity.unblockInteraction() + buttonState?.value = FooterButtonState.ENABLED + } else { + buttonState?.value = FooterButtonState.LOADING + } + // action on basis of type redirect to onclick + ctaData?.let { + if (ctaData.url == DISMISS) { + sharedVM.updateBottomSheetState( + state = HpBottomSheetState.Hidden + ) + } else if ( + it.toActionData().type == PAYMENT_MODE_CHANGED + ) { + multiBottomSheetData.apply { + forEach { + if (it.id in listOf(UPI, NET_BANKING)) { + it.containerList?.forEach { + container -> + container.highlightSelection = + false + } + } else if ( + it.id == CHANGE_PAYMENT_MODE + ) { + it.containerList?.forEach { + container -> + container.preSelected = + (container.id == + selectedItem) + } + } + } + } + handleMultiBottomSheetAction( + multiBottomSheetData, + ActionData( + url = SHOW_BOTTOMSHEET, + type = selectedItem + ), + activity, + sharedVM, + investmentsTabVm, + listener, + paymentManager = paymentManager, + dashboardSharedVM = dashboardSharedVM + ) + ?.let { it1 -> + listener?.invoke(it1 as ActionData) + } + } else { + activity.hopper.startProcess( + activity = activity, + ctaData = it, + onResult = { ctaData -> + buttonState?.value = + FooterButtonState.ENABLED + sharedVM.updateBottomSheetState( + state = HpBottomSheetState.Hidden + ) + if ( + ctaData?.url == + AMC_FUND_AUTOPAY_SETUP_V3 + ) { + initializePaymentActivity( + ctaData = ctaData, + paymentManager = paymentManager, + activity = activity, + dashboardSharedVM = + dashboardSharedVM, + investmentsTabVm = + investmentsTabVm + ) + } else { + ctaData?.let { ctaData -> + navigateTo( + ctaData = ctaData, + activity = activity + ) + } + } + } + ) + } + } + } + ), + state = HpBottomSheetState.Visible, + content = + HpBottomSheetContent( + composableType = + HpBottomSheetComposableType + .INVESTMENT_TAB_GENERIC_MULTI_BOTTOMSHEET, + renderType = HpBottomSheetRenderType.NATIVE, + nativeContent = it + ) + ) + } + } else {} + } + else -> {} + } + } + return null + } + + private fun initializePaymentActivity( + ctaData: CtaData?, + paymentManager: PaymentManager, + activity: HomePageActivity, + dashboardSharedVM: DashboardSharedVM, + investmentsTabVm: InvestmentsVm + ) { + ctaData?.let { + if (ctaData.url.equals(AMC_FUND_AUTOPAY_SETUP_V3)) { + investmentsTabVm.paymentCta = ctaData.url + val rawResponse = + ctaData.url?.let { it1 -> TemporaryStorageHelper.SOFT_REF_CACHE.get(it1) } + val response = rawResponse as? AdditionalDataAsyncResponse? + NaviTrackEvent.trackEvent( + AmcAnalytics.AMC_PAYMENT_TOKEN_RECEIVED, + hashMapOf( + PaymentAnalytics.SYNC to response?.syncFlow?.orFalse().toString(), + PaymentAnalytics.IS_TOKEN_VALID to + response?.tokenDetails?.naviSdkToken.isNotNullAndNotEmpty().toString() + ) + ) + investmentsTabVm.autopayPaymentInitiateData = response + dashboardSharedVM.isAmcPayment = true + if (response?.syncFlow.orFalse()) { + dashboardSharedVM.isSyncFlow = true + if (response?.tokenDetails?.naviSdkToken.isNotNullAndNotEmpty()) { + dashboardSharedVM.syncFlowPaymentInitStartTime = System.currentTimeMillis() + NaviTrackEvent.trackEvent(AmcAnalytics.AMC_INIT_PAYMENT) + paymentManager.clear() + paymentManager.initActivity(activity = activity) + paymentManager.getPaymentMethodsV2( + response?.tokenDetails?.naviSdkToken!!, + PaymentPrefetchMethodRequest() + ) + } + } + } + } + } } diff --git a/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/InvestmentsTab.kt b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/InvestmentsTab.kt index 5852323e6a..7504f548ab 100644 --- a/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/InvestmentsTab.kt +++ b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/InvestmentsTab.kt @@ -21,17 +21,24 @@ import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.distinctUntilChanged +import com.navi.amc.utils.Constant +import com.navi.base.model.ActionData import com.navi.base.utils.ConnectivityObserver import com.navi.base.utils.ConnectivityObserverImpl import com.navi.base.utils.isNotNull import com.navi.common.utils.parseColor import com.navi.common.utils.setStatusBarColorInt import com.navi.common.utils.toActionData +import com.navi.common.utils.toCtaData import com.navi.naviwidgets.extensions.FloatingButtonOverlay import com.navi.naviwidgets.extensions.isScrollingDown import com.navi.naviwidgets.models.FooterButtonState +import com.navi.payment.utils.PaymentAnalytics +import com.navi.paymentclients.viewmodel.base.PaymentManager import com.naviapp.R import com.naviapp.common.viewmodel.BottomNavBarVM +import com.naviapp.dashboard.viewmodels.DashboardSharedVM import com.naviapp.home.common.hopperProcessor.processHandlerImpl.Hopper import com.naviapp.home.compose.activity.HomePageActivity import com.naviapp.home.compose.model.BottomStickyNudgeState @@ -42,6 +49,7 @@ import com.naviapp.utils.Constants.LAST_UPDATED import com.naviapp.utils.Constants.RANK_OF_SECTION import com.naviapp.utils.Constants.WHITE_COLOR import com.naviapp.utils.formatTimeStamp +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged @OptIn(ExperimentalFoundationApi::class) @@ -53,7 +61,9 @@ fun InvestmentsTab( investmentsScreenHelper: InvestmentsScreenHelper, hopper: Hopper, toggleStatusBarColor: (String) -> Unit, - bottomNavBarVM: BottomNavBarVM + bottomNavBarVM: BottomNavBarVM, + paymentManager: PaymentManager, + dashboardSharedVM: DashboardSharedVM ) { val connectivityObserver: ConnectivityObserver by lazy { @@ -67,6 +77,26 @@ fun InvestmentsTab( val internetConnectivityStatus = sharedVM.internetConnectivityStatus.collectAsStateWithLifecycle(false) + LaunchedEffect(key1 = Unit) { + dashboardSharedVM.postPaymentStatus.collectLatest { ctaData -> + ctaData?.let { postPaymentStatus -> + val url = investmentsTabVm.getRedirectionUrlForCta() + var bundle = investmentsTabVm.getRedirectionBundleForCta() + bundle?.apply { + putString(PaymentAnalytics.PROVIDER, postPaymentStatus.provider) + putParcelable(Constant.PAYMENT_DATA, postPaymentStatus) + } + investmentsScreenHelper.navigateTo( + ctaData = ActionData(url = url).toCtaData(), + activity = activity, + bundle = bundle + ) + dashboardSharedVM.isAmcPayment = false + dashboardSharedVM.setPostPaymentResponseStatus(null) + } + } + } + LaunchedEffect(scrollState.firstVisibleItemIndex) { if (sharedVM.getSelectedTabId() == BottomBarTabType.INVESTMENT.name) { snapshotFlow { scrollState.firstVisibleItemIndex } @@ -144,7 +174,13 @@ fun InvestmentsTab( sharedVM, investmentsTabVm.getBottomSheetData(actionData?.type), index, - investmentsTabVm + investmentsTabVm, + multiBottomSheetData = + investmentsTabVm.getMultiBottomSheetData( + actionData?.type + ), + paymentManager = paymentManager, + dashboardSharedVM = dashboardSharedVM ) }, onVisible = { genericAnalytics -> @@ -172,7 +208,9 @@ fun InvestmentsTab( actionData?.type ), index, - investmentsTabVm + investmentsTabVm, + paymentManager = paymentManager, + dashboardSharedVM = dashboardSharedVM ) } ) @@ -192,7 +230,9 @@ fun InvestmentsTab( activity, sharedVM, investmentsTabVm.getBottomSheetData(actionData?.type), - investmentsTabVm = investmentsTabVm + investmentsTabVm = investmentsTabVm, + paymentManager = paymentManager, + dashboardSharedVM = dashboardSharedVM ) } ) diff --git a/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/genericComposables/PaymentCardComposable.kt b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/genericComposables/PaymentCardComposable.kt index cc72c730f0..a4163fe362 100644 --- a/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/genericComposables/PaymentCardComposable.kt +++ b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/genericComposables/PaymentCardComposable.kt @@ -88,6 +88,12 @@ fun PaymentCardComposable( paymentCard.bottomSheetData?.let { investmentsTabVm.setBottomSheetData(paymentCard.actionData?.type, it) } + paymentCard.multiBottomSheetData?.let { multiBottomSheet -> + investmentsTabVm.setMultiBottomSheetData( + multiBottomSheetData = multiBottomSheet, + bottomSheetType = paymentCard.actionData?.type + ) + } onClick(paymentCard.actionData) } ) diff --git a/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/genericComposables/SipAutoPayCardComposable.kt b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/genericComposables/SipAutoPayCardComposable.kt index e14e12de1e..1b05cd20b6 100644 --- a/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/genericComposables/SipAutoPayCardComposable.kt +++ b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/genericComposables/SipAutoPayCardComposable.kt @@ -111,34 +111,44 @@ fun SipAutoPayCardComposable( textFieldData = it.subtitleText, modifier = Modifier.setPadding(it.properties?.subtitleProperty?.padding) ) - NaviTextWidgetized( - textFieldData = it.projectedText, - modifier = - Modifier.setPadding(it.properties?.projectedTextProperty?.padding) - ) - Box( - modifier = Modifier.setPadding(it.properties?.buttonProperty?.padding) - ) { - Box( - modifier = - Modifier.setBackground( - it.properties?.buttonProperty?.backgroundColor, - it.properties?.buttonProperty?.shape, - it.properties?.buttonProperty?.backGroundBrushData - ) - .setPadding(it.properties?.buttonProperty?.padding) - ) { + Row { + Column { NaviTextWidgetized( - textFieldData = it.buttonText, + textFieldData = it.projectedText, + modifier = + Modifier.setPadding( + it.properties?.projectedTextProperty?.padding + ) ) + Box( + modifier = + Modifier.setPadding(it.properties?.buttonProperty?.padding) + ) { + Box( + modifier = + Modifier.setBackground( + it.properties?.buttonProperty?.backgroundColor, + it.properties?.buttonProperty?.shape, + it.properties + ?.buttonProperty + ?.backGroundBrushData + ) + .setPadding(it.properties?.buttonProperty?.padding) + ) { + NaviTextWidgetized( + textFieldData = it.buttonText, + ) + } + } + } + Column( + modifier = + Modifier.setPadding(it.properties?.sipIconProperty?.padding) + ) { + NaviImage(imageFieldData = it.sipIcon) } } } - Column( - modifier = Modifier.setPadding(it.properties?.sipIconProperty?.padding) - ) { - NaviImage(imageFieldData = it.sipIcon) - } } Row( horizontalArrangement = diff --git a/android/app/src/main/java/com/naviapp/home/dashboard/viewmodels/InvestmentsVm.kt b/android/app/src/main/java/com/naviapp/home/dashboard/viewmodels/InvestmentsVm.kt index 32c485bd36..be9b00441d 100644 --- a/android/app/src/main/java/com/naviapp/home/dashboard/viewmodels/InvestmentsVm.kt +++ b/android/app/src/main/java/com/naviapp/home/dashboard/viewmodels/InvestmentsVm.kt @@ -8,11 +8,15 @@ package com.naviapp.home.dashboard.viewmodels import BottomSheetData +import android.os.Bundle import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.navi.amc.common.model.AdditionalDataAsyncResponse +import com.navi.amc.common.model.NextCtaResponse +import com.navi.amc.utils.Constant import com.navi.analytics.utils.NaviTrackEvent import com.navi.ap.utils.constants.WHITE_HEX_CODE import com.navi.base.cache.model.NaviCacheEntity @@ -22,15 +26,19 @@ import com.navi.base.model.CtaData import com.navi.base.utils.isNotNull import com.navi.base.utils.orFalse import com.navi.common.checkmate.model.MetricInfo +import com.navi.common.model.GenericBottomSheetData import com.navi.common.model.ModuleNameV2 import com.navi.common.model.common.InvestmentTabNudgeData import com.navi.common.network.models.ErrorUnifiedResponse import com.navi.common.network.models.GenericErrorResponse +import com.navi.common.utils.Constants.AMC_FUND_AUTOPAY_SETUP_V3 +import com.navi.common.utils.Constants.AUTOPAY_PAYMENT_CALLBACK_SYNC import com.navi.common.utils.isNetworkAvailable import com.navi.common.utils.isValidResponse import com.navi.common.utils.toCtaData import com.navi.common.viewmodel.BaseVM import com.navi.naviwidgets.models.response.TextFieldData +import com.navi.payment.utils.PaymentAnalytics import com.naviapp.home.dashboard.models.investmentTabWidgetData.CutOffSlotData import com.naviapp.home.dashboard.models.investmentTabWidgetData.CutOffTimerWidget import com.naviapp.home.dashboard.models.response.GenericComposableWidgetInfo @@ -96,8 +104,11 @@ constructor( var cutOffTimerCompositionCount = 0 var bottomSheetType: String? = null + var multiBottomSheetType: String? = null private val bottomSheetDataFromType: MutableMap = mutableMapOf() + private val multiBottomSheetDataFromType: MutableMap = + mutableMapOf() private val _dynamicCta = MutableSharedFlow(replay = 0, extraBufferCapacity = 1) val dynamicCta: SharedFlow = _dynamicCta @@ -115,6 +126,10 @@ constructor( val bottomStickyNudgeData: StateFlow get() = _bottomStickyNudgeData + var autopayPaymentInitiateData: AdditionalDataAsyncResponse? = null + + var paymentCta: String? = null + init { recordScreenLandTime(INVESTMENT_TAB_SCREEN_V3) } @@ -311,6 +326,18 @@ constructor( bottomSheetType?.let { bottomSheetDataFromType[it] = bottomSheetData } } + fun setMultiBottomSheetData( + multiBottomSheetData: GenericBottomSheetData?, + bottomSheetType: String? + ) { + this.multiBottomSheetType = bottomSheetType + bottomSheetType?.let { multiBottomSheetDataFromType[it] = multiBottomSheetData } + } + + fun getMultiBottomSheetData(bottomSheetType: String?): GenericBottomSheetData? { + return multiBottomSheetDataFromType[bottomSheetType] + } + fun getBottomSheetData(bottomSheetType: String?): BottomSheetData? { return bottomSheetDataFromType[bottomSheetType] } @@ -349,6 +376,42 @@ constructor( eventFiredMap[eventName] = true } + fun getRedirectionUrlForCta(): String? { + return when (paymentCta) { + AMC_FUND_AUTOPAY_SETUP_V3 -> AUTOPAY_PAYMENT_CALLBACK_SYNC + else -> null + } + } + + fun getRedirectionBundleForCta(): Bundle? { + return when (paymentCta) { + AMC_FUND_AUTOPAY_SETUP_V3 -> { + val bundle = + Bundle().apply { + putString( + Constant.TRANSACTION_ID, + autopayPaymentInitiateData?.tokenDetails?.transactionId + ) + putString( + Constant.NAVI_SDK_TOKEN, + autopayPaymentInitiateData?.tokenDetails?.naviSdkToken + ) + putParcelable( + Constant.REQUEST_CONFIG, + autopayPaymentInitiateData?.requestConfig + ) + putString(PaymentAnalytics.SYNC, PaymentAnalytics.TRUE) + putString( + Constant.ORDER_ID, + autopayPaymentInitiateData?.tokenDetails?.naviOrderId + ) + } + bundle + } + else -> null + } + } + suspend fun getLastUpdatedTime(): Long { return naviCacheRepository.getLastUpdatedTime(NaviSharedDbKeys.INVESTMENT_TAB.keyName) } diff --git a/android/app/src/main/java/com/naviapp/home/model/HpBottomSheetStateHolder.kt b/android/app/src/main/java/com/naviapp/home/model/HpBottomSheetStateHolder.kt index e84f8303a7..ac46748c82 100644 --- a/android/app/src/main/java/com/naviapp/home/model/HpBottomSheetStateHolder.kt +++ b/android/app/src/main/java/com/naviapp/home/model/HpBottomSheetStateHolder.kt @@ -15,6 +15,7 @@ import com.navi.base.model.ActionData import com.navi.base.model.CtaData import com.navi.common.alchemist.model.AlchemistBottomSheetStructure import com.navi.common.alchemist.model.AlchemistWidgetModelDefinition +import com.navi.common.model.AmcBottomSheetData import com.navi.naviwidgets.models.FooterButtonState import com.navi.uitron.model.UiTronResponse @@ -57,6 +58,19 @@ data class InvestmentBottomSheetData( }, ) : HpBottomSheetConfig() +data class AmcGenericBottomSheetData( + val onSuccess: (actionData: ActionData?, bundle: Bundle?) -> Unit = { _, _ -> }, + val onHopperStart: + ( + ctaData: CtaData?, + buttonState: MutableState?, + bottomSheetData: AmcBottomSheetData?, + selectedItem: String? + ) -> Unit = + { _, _, _, _ -> + }, +) : HpBottomSheetConfig() + enum class HpBottomSheetState { Visible, Hidden, @@ -69,5 +83,6 @@ enum class HpBottomSheetRenderType { enum class HpBottomSheetComposableType { INVESTMENT_TAB_GENERIC_BOTTOMSHEET, + INVESTMENT_TAB_GENERIC_MULTI_BOTTOMSHEET, NOTIFICATION_PERMISSION_BOTTOMSHEET } diff --git a/android/app/src/main/java/com/naviapp/screenOverlay/bottomsheet/ui/HomeScreenBottomSheet.kt b/android/app/src/main/java/com/naviapp/screenOverlay/bottomsheet/ui/HomeScreenBottomSheet.kt index 74d0b7abb4..a2e571f342 100644 --- a/android/app/src/main/java/com/naviapp/screenOverlay/bottomsheet/ui/HomeScreenBottomSheet.kt +++ b/android/app/src/main/java/com/naviapp/screenOverlay/bottomsheet/ui/HomeScreenBottomSheet.kt @@ -35,6 +35,8 @@ import com.navi.ap.utils.constants.BOTTOM_SHEET_VISIBILITY import com.navi.base.model.CtaData import com.navi.base.utils.orTrue import com.navi.common.alchemist.model.AlchemistBottomSheetStructure +import com.navi.common.model.AmcBottomSheetData +import com.navi.common.ui.compose.GenericComposableBottomSheet import com.navi.common.uitron.model.action.CtaAction import com.navi.common.uitron.render.CommonCustomUiTronRenderer import com.navi.common.utils.CommonNaviAnalytics @@ -47,6 +49,7 @@ import com.navi.uitron.utils.hexToComposeColor import com.naviapp.appsettings.model.notificationSettings.NotificationSettingsBottomSheetType import com.naviapp.appsettings.ui.bottomSheets.NotificationSettingBottomSheetContent import com.naviapp.home.dashboard.ui.compose.investmentTab.genericComposables.BottomSheetTypeComposable +import com.naviapp.home.model.AmcGenericBottomSheetData import com.naviapp.home.model.HomeCtaTypes import com.naviapp.home.model.HpBottomSheetComposableType import com.naviapp.home.model.HpBottomSheetConfig @@ -195,6 +198,21 @@ private fun BottomSheetContentRenderer( ) } // Handle different composable types here using HpBottomSheetComposableType enum + HpBottomSheetComposableType.INVESTMENT_TAB_GENERIC_MULTI_BOTTOMSHEET -> { + val configData = config as? AmcGenericBottomSheetData + GenericComposableBottomSheet( + bottomSheetData = content.nativeContent as AmcBottomSheetData, + onClick = { actionData -> + viewModel.updateBottomSheetState(state = HpBottomSheetState.Hidden) + configData?.onSuccess?.invoke(actionData, null) + }, + onHopperStart = { ctaData, buttonState, selectedItem -> + configData + ?.onHopperStart + ?.invoke(ctaData, buttonState, content.nativeContent, selectedItem) + } + ) + } else -> Unit } } diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/activity/CheckerActivity.kt b/android/navi-amc/src/main/java/com/navi/amc/common/activity/CheckerActivity.kt index d5b5a2bd95..2ee60ee0cf 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/common/activity/CheckerActivity.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/common/activity/CheckerActivity.kt @@ -59,8 +59,12 @@ import com.navi.amc.utils.Constant.OTP_FLOW_TYPE_SIP_AUTOPAY import com.navi.amc.utils.Constant.OTP_FLOW_TYPE_SIP_MANUAL import com.navi.amc.utils.Constant.PAYMENT_CALLBACK_ERROR import com.navi.amc.utils.Constant.REQUEST_CONFIG +import com.navi.amc.utils.Constant.SETUP_AUTOPAY_ALL_SIP +import com.navi.amc.utils.Constant.SETUP_AUTOPAY_EXISTING_SIP +import com.navi.amc.utils.Constant.SETUP_SIP_AUTOPAY import com.navi.amc.utils.Constant.SIP_REFERENCE_ID import com.navi.amc.utils.Constant.TRANSACTION_ID +import com.navi.amc.utils.Constant.TRANSACTION_REFERENCE_ID import com.navi.amc.utils.SubPageStatusType import com.navi.amc.utils.TempStorageHelper import com.navi.amc.utils.amcDeeplink @@ -71,6 +75,8 @@ import com.navi.amc.utils.toNavigateAmcModule import com.navi.base.model.ActionData import com.navi.base.model.CtaData import com.navi.base.model.LineItem +import com.navi.base.utils.FAILURE +import com.navi.base.utils.SUCCESS import com.navi.base.utils.isNotNull import com.navi.base.utils.isNotNullAndNotEmpty import com.navi.base.utils.orFalse @@ -127,7 +133,13 @@ class CheckerActivity : BasePaymentActivity() { type = intent.getStringExtra(Constants.SECOND_IDENTIFIER).orEmpty() if ( intent.getStringExtra(FLOW_TYPE) in - listOf(OTP_FLOW_TYPE_SIP_MANUAL, OTP_FLOW_TYPE_SIP_AUTOPAY) + listOf( + OTP_FLOW_TYPE_SIP_MANUAL, + OTP_FLOW_TYPE_SIP_AUTOPAY, + SETUP_SIP_AUTOPAY, + SETUP_AUTOPAY_EXISTING_SIP, + SETUP_AUTOPAY_ALL_SIP + ) ) { binding.loaderView.updateLayoutParams { height = dpToPxInInt(148) @@ -210,7 +222,8 @@ class CheckerActivity : BasePaymentActivity() { if (type == PAYMENT_CALLBACK_SYNC || type == AUTO_PAY_PAYMENT_CALLBACK_SYNC) { val requestId = intent.getStringExtra(TRANSACTION_ID) val requestConfig = intent.getParcelableExtra(REQUEST_CONFIG) - requestId?.let { apiPollInit(requestConfig, requestId) } + val tranReferenceId = intent.getStringExtra(TRANSACTION_REFERENCE_ID) + requestId?.let { apiPollInit(requestConfig, requestId, tranReferenceId) } } if (type == PENNY_DROP) { lifecycleScope.launch { @@ -345,7 +358,11 @@ class CheckerActivity : BasePaymentActivity() { } } - private fun apiPollInit(requestConfig: RequestConfig?, requestId: String?) { + private fun apiPollInit( + requestConfig: RequestConfig?, + requestId: String?, + tranReferenceId: String? = null + ) { apiPollScheduler = ApiPollScheduler( initialDelay = requestConfig?.initialDelay.orValue(0).toLong(), @@ -357,7 +374,12 @@ class CheckerActivity : BasePaymentActivity() { .toLong(), doOnTimeout = { runOnUiThread { handleTimeOutError() } } ) { - viewModel.checkApiPollStatus(requestId.orEmpty(), type, screenName = screenName) + viewModel.checkApiPollStatus( + requestId.orEmpty(), + type, + tranReferenceId, + screenName = screenName + ) } pollingStartTime = System.currentTimeMillis() apiPollScheduler?.scheduleApiPoll() @@ -367,7 +389,7 @@ class CheckerActivity : BasePaymentActivity() { response: RepoResult>? ) { if (response?.data?.status.isNullOrEmpty()) { - sendAnalyticsEvent(FAILURE) + sendAnalyticsEvent(FIREBASE_FAILURE) if (response?.errors?.firstOrNull() != null) { onFailureResponse(response.errors?.firstOrNull()) } else { @@ -375,8 +397,8 @@ class CheckerActivity : BasePaymentActivity() { } } when (response?.data?.status) { - SUCCESS, - FAILURE -> { + FIREBASE_SUCCESS, + FIREBASE_FAILURE -> { sendAnalyticsEvent(response.data?.status) sendKYCStatusEvent(response.data?.status, response.data?.data?.nextCTA?.url) apiPollScheduler?.stopApiPoll() @@ -420,7 +442,7 @@ class CheckerActivity : BasePaymentActivity() { } } else if ( intent.getStringExtra(FLOW_TYPE).equals(OTP_FLOW_TYPE_SIP_AUTOPAY) && - response.data?.status == SUCCESS + response.data?.status == FIREBASE_SUCCESS ) { val sipDetailsData = SipDetailsData( @@ -437,7 +459,14 @@ class CheckerActivity : BasePaymentActivity() { viewModel.postSipDetails(sipDetailsData) } else if ( intent.getStringExtra(FLOW_TYPE).equals(OTP_FLOW_TYPE_SIP_MANUAL) && - response.data?.status == SUCCESS && + response.data?.status == FIREBASE_SUCCESS && + intent.getStringExtra(SIP_REFERENCE_ID).isNotNullAndNotEmpty() + ) { + val sipRefId = intent?.getStringExtra(SIP_REFERENCE_ID) + viewModel.getSipSuccessPage(sipRefId) + } else if ( + intent.getStringExtra(FLOW_TYPE).equals(SETUP_SIP_AUTOPAY) && + response.data?.status == FIREBASE_SUCCESS && intent.getStringExtra(SIP_REFERENCE_ID).isNotNullAndNotEmpty() ) { val sipRefId = intent?.getStringExtra(SIP_REFERENCE_ID) @@ -461,7 +490,15 @@ class CheckerActivity : BasePaymentActivity() { intent.getStringExtra(AUTOPAY_TYPE).orEmpty() ) } else { - onFailureResponse(response.errors?.firstOrNull()) + onFailureResponse(response.errors?.firstOrNull(), response.data?.status) + } + } + FIREBASE_PENDING -> { + if ( + intent.getStringExtra(FLOW_TYPE).equals(SETUP_SIP_AUTOPAY) && + intent.getStringExtra(ORDER_ID).isNotNullAndNotEmpty() + ) { + naviOrderId = intent.getStringExtra(ORDER_ID) } } } @@ -572,7 +609,7 @@ class CheckerActivity : BasePaymentActivity() { } private fun sendKYCStatusEvent(status: String?, url: String?) { - if (status == SUCCESS && type == CVL_KYC_CHECK) { + if (status == FIREBASE_SUCCESS && type == CVL_KYC_CHECK) { if (url == amcDeeplink(NaviAmcDeeplinkNavigator.CHECKER.plus("/").plus(INITIATE_KYC))) { sendEvent(AmcAnalytics.AMC_INIT_USER_NEW_KYC, null) } else { @@ -581,10 +618,10 @@ class CheckerActivity : BasePaymentActivity() { } } - private fun onFailureResponse(response: GenericErrorResponse?) { + private fun onFailureResponse(response: GenericErrorResponse?, status: String? = null) { response?.let { apiPollScheduler?.stopApiPoll() - val url = getCta() + val url = getCta(status) val bundle = intent.extras ?: Bundle() bundle.putParcelable(ERROR_DATA, it) if (errorAllowed.toBoolean()) { @@ -629,7 +666,7 @@ class CheckerActivity : BasePaymentActivity() { } } - private fun getCta(): String { + private fun getCta(status: String? = null): String { return when (type) { CVL_KYC_CHECK -> amcDeeplink(NaviAmcDeeplinkNavigator.KYC.plus("/").plus(SubPageStatusType.PAN)) @@ -669,9 +706,21 @@ class CheckerActivity : BasePaymentActivity() { AUTO_PAY_PAYMENT_INIT, AUTO_PAY_PAYMENT_CALLBACK, AUTO_PAY_PAYMENT_CALLBACK_SYNC -> { - viewModel.isNavigationRequiredOnFinish = false - finish() - EMPTY + if ( + intent.getStringExtra(FLOW_TYPE).equals(SETUP_SIP_AUTOPAY) && + naviOrderId.isNotNullAndNotEmpty() && + status != FIREBASE_SUCCESS + ) { + intent.putExtra(ORDER_ID, naviOrderId) + intent.putExtra(Constant.DATA_SOURCE, Constant.PURCHASE_ORDER) + amcDeeplink( + NaviAmcDeeplinkNavigator.FUND.plus("/").plus(SubPageStatusType.ORDER_STATUS) + ) + } else { + viewModel.isNavigationRequiredOnFinish = false + finish() + EMPTY + } } else -> "" } @@ -691,7 +740,7 @@ class CheckerActivity : BasePaymentActivity() { private fun sendAnalyticsEvent(status: String? = null) { var latencyEventName: String? = null - if (status == SUCCESS) { + if (status == FIREBASE_SUCCESS) { when (type) { CVL_KYC_CHECK -> { latencyEventName = AMC_LATENCY_PAN_CHECK_TIME @@ -758,10 +807,10 @@ class CheckerActivity : BasePaymentActivity() { latencyEventName, pollingStartTime, System.currentTimeMillis(), - SUCCESS + FIREBASE_SUCCESS ) } - } else if (status == FAILURE) { + } else if (status == FIREBASE_FAILURE) { when (type) { CVL_KYC_CHECK -> { latencyEventName = AMC_LATENCY_PAN_CHECK_TIME @@ -824,7 +873,7 @@ class CheckerActivity : BasePaymentActivity() { latencyEventName, pollingStartTime, System.currentTimeMillis(), - FAILURE + FIREBASE_FAILURE ) } } else { @@ -963,7 +1012,10 @@ class CheckerActivity : BasePaymentActivity() { const val TYPE = "TYPE" const val ERROR_TAG = "ERROR_TAG" const val STATUS = "status" - const val SUCCESS = "SUCCESS" - const val FAILURE = "FAILURE" + const val SUCCESS = "success" + const val FAILURE = "failure" + const val FIREBASE_SUCCESS = "SUCCESS" + const val FIREBASE_FAILURE = "FAILURE" + const val FIREBASE_PENDING = "PENDING" } } diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/fragment/AmcBaseFragment.kt b/android/navi-amc/src/main/java/com/navi/amc/common/fragment/AmcBaseFragment.kt index 9f5bbcd6f1..39bebf0416 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/common/fragment/AmcBaseFragment.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/common/fragment/AmcBaseFragment.kt @@ -18,9 +18,19 @@ import com.navi.amc.common.taskProcessor.AmcTaskManager import com.navi.amc.databinding.AmcBaseFragmentLayoutBinding import com.navi.amc.fundbuy.viewmodel.FundBuyFlowViewModel import com.navi.amc.utils.AmcAnalytics +import com.navi.amc.utils.Constant.CHANGE_PAYMENT_MODE +import com.navi.amc.utils.Constant.DATA +import com.navi.amc.utils.Constant.DISMISS +import com.navi.amc.utils.Constant.NET_BANKING +import com.navi.amc.utils.Constant.PAYMENT_MODE +import com.navi.amc.utils.Constant.PAYMENT_MODE_CHANGED +import com.navi.amc.utils.Constant.SHOW_BOTTOMSHEET +import com.navi.amc.utils.Constant.UPI import com.navi.amc.utils.TempStorageHelper +import com.navi.base.model.ActionData import com.navi.base.model.GenericAnalyticsData import com.navi.common.listeners.HeaderInteractionListener +import com.navi.common.model.AmcBottomSheetData import com.navi.common.model.Header import com.navi.common.ui.fragment.BaseFragment import com.navi.naviwidgets.extensions.showWhenDataIsAvailable @@ -185,4 +195,78 @@ abstract class AmcBaseFragment : BaseFragment() { infiniteRepeat = infiniteRepeat ) } + + fun handleMultiBottomSheetAction( + action: ActionData?, + orderSummaryBottomSheetList: List? = null, + listener: ((ActionData) -> Unit)? = null + ): Any? { + // takes in an actionData and shows back to back bottom sheets based on the urls and types + // of + // each bottom sheet + sendEvent(action?.metaData?.clickedData) + if (action?.type == DISMISS) return null + when (action?.url) { + SHOW_BOTTOMSHEET -> { + if (action.type in listOf(UPI, NET_BANKING, CHANGE_PAYMENT_MODE)) { + if (action.type in listOf(UPI, NET_BANKING)) { + buyFlowVM.paymentMode.value = action.type + } else { + orderSummaryBottomSheetList + ?.firstOrNull { it.id == CHANGE_PAYMENT_MODE } + ?.apply { + containerList?.forEach { container -> + container.preSelected = + (container.id == buyFlowVM.paymentMode.value) && + (container.isClickable == true) + } + } + } + val bottomSheet = + orderSummaryBottomSheetList?.firstOrNull { it.id == action.type } + bottomSheet?.let { + val bundle = Bundle().apply { putParcelable(DATA, it) } + AmcCommonComposableBottomSheet.newInstance( + bundle = bundle, + action = { + if (it.url == DISMISS) { + listener?.invoke(it) + } else { + handleMultiBottomSheetAction( + action = it, + orderSummaryBottomSheetList = + orderSummaryBottomSheetList, + listener = listener + ) + ?.let { it1 -> listener?.invoke(it1 as ActionData) } + } + } + ) + .apply { safelyShowBottomSheet(this, AmcCommonBottomSheet.TAG) } + } + } else if (action.type == PAYMENT_MODE_CHANGED) { + buyFlowVM.paymentMode.value = + action.parameters?.firstOrNull { it.key == PAYMENT_MODE }?.value + orderSummaryBottomSheetList + ?.firstOrNull { it.id == CHANGE_PAYMENT_MODE } + ?.apply { + containerList?.forEach { container -> + container.preSelected = + (container.id == buyFlowVM.paymentMode.value) + } + } + handleMultiBottomSheetAction( + action = + ActionData(url = SHOW_BOTTOMSHEET, type = buyFlowVM.paymentMode.value), + orderSummaryBottomSheetList = orderSummaryBottomSheetList, + listener = listener + ) + } + } + else -> { + return action + } + } + return null + } } diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/fragment/AmcCommonBottomSheet.kt b/android/navi-amc/src/main/java/com/navi/amc/common/fragment/AmcCommonBottomSheet.kt index d27958c6ce..3e9e7c9b47 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/common/fragment/AmcCommonBottomSheet.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/common/fragment/AmcCommonBottomSheet.kt @@ -52,12 +52,15 @@ class AmcCommonBottomSheet : BaseBottomSheet() { rightIcon.setOnClickListener { safelyDismissDialog() } title.setSpannableString(response.title) subtitle.setSpannableString(response.subTitle, ::onSpanClick) - noteBanner.background = - getNaviDrawable( - cornerRadius = dpToPxInInt(4), - backgroundColor = response.note?.bgColor.parseColorSafe() - ) - noteBanner.setSpannableString(response.note?.title) + response?.note?.let { + noteBanner.background = + getNaviDrawable( + cornerRadius = dpToPxInInt(4), + backgroundColor = response.note.bgColor.parseColorSafe() + ) + noteBannerLeftIcon.showWhenDataIsAvailable(response.note.leftIconCode) + noteBannerText.setSpannableString(response.note.title) + } ?: run { noteBanner.visibility = View.GONE } imageBanner.showWhenDataIsAvailable(response.imageUrl) setFooter(response.horizontalActions, response.actionData) AmcAnalytics.sendEvent( @@ -122,10 +125,10 @@ class AmcCommonBottomSheet : BaseBottomSheet() { } override val screenName: String - get() = SubPageStatusType.AMC_COMMON_BOTTOMSHEET + get() = SubPageStatusType.AMC_COMMON_BOTTOMSHEET_V2 companion object { - const val TAG = "AMC_COMMON_BOTTOM_SHEET" + const val TAG = "AMC_COMMON_BOTTOM_SHEET_V2" @JvmStatic fun newInstance( diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/fragment/AmcCommonComposableBottomSheet.kt b/android/navi-amc/src/main/java/com/navi/amc/common/fragment/AmcCommonComposableBottomSheet.kt new file mode 100644 index 0000000000..f52518add4 --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/common/fragment/AmcCommonComposableBottomSheet.kt @@ -0,0 +1,78 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.common.fragment + +import android.os.Bundle +import android.view.ViewStub +import androidx.databinding.DataBindingUtil +import androidx.fragment.app.activityViewModels +import com.navi.amc.R +import com.navi.amc.databinding.AmcCommonComposableBottomsheetBinding +import com.navi.amc.utils.Constant +import com.navi.base.model.ActionData +import com.navi.common.model.AmcBottomSheetData +import com.navi.common.ui.compose.GenericComposableBottomSheet +import com.navi.common.ui.fragment.BaseBottomSheet +import com.navi.common.viewmodel.GenericBottomSheetVM + +class AmcCommonComposableBottomSheet : BaseBottomSheet() { + private val viewModel: GenericBottomSheetVM by activityViewModels() + private lateinit var binding: AmcCommonComposableBottomsheetBinding + private var action: ((ActionData) -> Unit)? = null + private var firstBottomSheet: AmcBottomSheetData? = null + private var secondBottomSheet: AmcBottomSheetData? = null + + override fun setContainerView(viewStub: ViewStub) { + viewStub.layoutResource = R.layout.amc_common_composable_bottomsheet + binding = DataBindingUtil.getBinding(viewStub.inflate())!! + initUI() + } + + private fun initUI() { + val bottomSheet = arguments?.getParcelable(Constant.DATA) + binding.root.setContent { + setPadding(0, 0, 0, 8) + GenericComposableBottomSheet( + bottomSheet, + changeListener = { handleAction(it) }, + action = { action, selectedItem -> handleAction(action, selectedItem) } + ) + } + + viewModel.actionData.observe(viewLifecycleOwner) { + it?.let { + handleAction(it) + viewModel.performAction(null) + } + } + } + + private fun handleAction(actionData: ActionData, key: String? = null) { + dismiss() + var cta = actionData.copy() + key?.let { cta.parameters?.firstOrNull { it.key == Constant.PAYMENT_MODE }?.value = key } + action?.invoke(cta) + } + + override val screenName = "AMC_GENERIC_BOTTOM_SHEET" + + companion object { + const val TAG = "AMC_GENERIC_BOTTOM_SHEET" + + @JvmStatic + fun newInstance( + bundle: Bundle = Bundle(), + action: ((ActionData) -> Unit)? = null + ): AmcCommonComposableBottomSheet { + return AmcCommonComposableBottomSheet().apply { + arguments = bundle + this.action = action + } + } + } +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/fragment/AmcDynamicBottomSheet.kt b/android/navi-amc/src/main/java/com/navi/amc/common/fragment/AmcDynamicBottomSheet.kt new file mode 100644 index 0000000000..7b276a1489 --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/common/fragment/AmcDynamicBottomSheet.kt @@ -0,0 +1,263 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.common.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewStub +import androidx.core.view.isVisible +import androidx.databinding.DataBindingUtil +import androidx.fragment.app.activityViewModels +import com.google.gson.Gson +import com.navi.amc.R +import com.navi.amc.common.model.AmcCommonBottomSheetData +import com.navi.amc.common.viewmodel.AmcCommonBottomSheetVM +import com.navi.amc.databinding.AmcCommonBottomsheetBinding +import com.navi.amc.utils.AmcAnalytics +import com.navi.amc.utils.Constant.AND_INSTALLMENT +import com.navi.amc.utils.Constant.CALENDAR_DATE +import com.navi.amc.utils.Constant.DATA +import com.navi.amc.utils.Constant.DISMISS +import com.navi.amc.utils.Constant.FIRST_INSTALLMENT_DATE +import com.navi.amc.utils.Constant.NEXT_INSTALLMENT_DATE +import com.navi.amc.utils.Constant.SIP_INSTALLMENT_CALENDAR_PREFIX +import com.navi.amc.utils.Constant.SIP_INSTALLMENT_CALENDAR_SUFFIX +import com.navi.amc.utils.SubPageStatusType +import com.navi.base.model.ActionData +import com.navi.base.model.LineItem +import com.navi.common.constants.UPDATE_CALENDAR_DATE +import com.navi.common.listeners.NewBottomSheetListener +import com.navi.common.ui.fragment.BaseBottomSheet +import com.navi.common.utils.observeNonNull +import com.navi.design.textview.NaviTextView +import com.navi.design.textview.model.TextWithStyle +import com.navi.design.utils.dpToPxInInt +import com.navi.design.utils.getNaviDrawable +import com.navi.design.utils.parseColorSafe +import com.navi.design.utils.setSpannableString +import com.navi.naviwidgets.callbacks.WidgetCallback +import com.navi.naviwidgets.databinding.CalendarWidgetLayoutBinding +import com.navi.naviwidgets.extensions.showWhenDataIsAvailable +import com.navi.naviwidgets.models.CalendarWidget + +class AmcDynamicBottomSheet : BaseBottomSheet() { + private val bottomSheetSharedVM: AmcCommonBottomSheetVM by activityViewModels() + private var listener: NewBottomSheetListener? = null + private lateinit var binding: AmcCommonBottomsheetBinding + private lateinit var calendarWidgetLayoutBinding: CalendarWidgetLayoutBinding + private var action: ((ActionData) -> Unit)? = null + private lateinit var widgetCallback: WidgetCallback + private var firstInstallmentDateMap: List? = null + private var nextInstallmentDateMap: List? = null + private var selectedDate: Int? = null + private var noteText: TextWithStyle? = null + + override fun setContainerView(viewStub: ViewStub) { + viewStub.layoutResource = R.layout.amc_common_bottomsheet + binding = DataBindingUtil.getBinding(viewStub.inflate())!! + val data = arguments?.getString(DATA) + val response = Gson().fromJson(data, AmcCommonBottomSheetData::class.java) + firstInstallmentDateMap = arguments?.getParcelableArrayList(FIRST_INSTALLMENT_DATE) + nextInstallmentDateMap = arguments?.getParcelableArrayList(NEXT_INSTALLMENT_DATE) + initObservers() + bottomSheetSharedVM.setVisibility(true) + initUI(response) + } + + private fun initUI(response: AmcCommonBottomSheetData) { + binding.apply { + icon.showWhenDataIsAvailable(response.iconCode) + rightIcon.showWhenDataIsAvailable(response.rightIconCode) + rightIcon.setOnClickListener { safelyDismissDialog() } + title.setSpannableString(response.title) + subtitle.setSpannableString(response.subTitle, ::onSpanClick) + response?.note?.let { + noteBanner.background = + getNaviDrawable( + cornerRadius = dpToPxInInt(4), + backgroundColor = response.note.bgColor.parseColorSafe() + ) + noteBannerLeftIcon.showWhenDataIsAvailable(response.note.leftIconCode) + noteBannerText.setSpannableString(response.note.title) + noteText = response.note.title + if (selectedDate != null) { + updateNoteBannerText(selectedDate) + } + } ?: run { noteBanner.visibility = View.GONE } + response.calendarData?.let { + binding.calendarWidget.visibility = View.VISIBLE + setCalendarData(response.calendarData, selectedDate) + } + imageBanner.showWhenDataIsAvailable(response.imageUrl) + setFooter(response.horizontalActions, response.actionData) + AmcAnalytics.sendEvent( + eventsData = response.metaData?.viewedData, + screenName = screenName + ) + } + } + + private fun setCalendarData( + calendarData: CalendarWidget?, + prevSelectedCalendarDate: Int? = null + ) { + prevSelectedCalendarDate?.let { calendarData?.widgetData?.selectedItemVal = it } + calendarData?.let { calendarWidget -> + calendarWidgetLayoutBinding = + CalendarWidgetLayoutBinding.inflate( + LayoutInflater.from(requireContext()), + binding.calendarWidget, + false + ) + if (calendarWidget.widgetData?.selectedItemVal != null) { + updateNoteBannerText(calendarWidget.widgetData?.selectedItemVal) + } + widgetCallback = + object : WidgetCallback { + fun onWidgetClick(actionData: ActionData) { + onSpanClick(actionData) + } + + override fun widgetUserData(key: String, value: Any) { + when (key) { + UPDATE_CALENDAR_DATE -> { + binding.calendarWidget.update( + widgetData = calendarWidget, + binding = calendarWidgetLayoutBinding, + widgetCallback = this, + position = 0 + ) + updateNoteBannerText(calendarWidget.widgetData?.selectedItemVal) + } + else -> {} + } + } + } + binding.calendarWidget.update( + widgetData = calendarWidget, + binding = calendarWidgetLayoutBinding, + widgetCallback = this.widgetCallback, + position = 0 + ) + + binding.calendarWidget.addView(calendarWidgetLayoutBinding.root) + } + } + + private fun updateNoteBannerText(selectedItemVal: Int?) { + selectedDate = selectedItemVal + val firstInstallmentDate = + firstInstallmentDateMap?.firstOrNull { it.key == selectedItemVal.toString() }?.value + val nextInstallmentDate = + nextInstallmentDateMap?.firstOrNull { it.key == selectedItemVal.toString() }?.value + val newTextWithStyle = + TextWithStyle( + text = + SIP_INSTALLMENT_CALENDAR_PREFIX + + firstInstallmentDate + + AND_INSTALLMENT + + nextInstallmentDate + + SIP_INSTALLMENT_CALENDAR_SUFFIX, + style = noteText?.style + ) + binding.noteBannerText.setSpannableString(newTextWithStyle) + } + + private fun onSpanClick(actionData: ActionData?) { + listener?.buttonClick(actionData) + safelyDismissDialog() + } + + private fun setFooter(isHorizontalAction: Boolean, actionData: ActionData?) { + binding.apply { + var secondaryActionView: NaviTextView? = null + if (isHorizontalAction) { + secondarybtn.visibility = View.GONE + secondarySideBtn.visibility = View.VISIBLE + secondaryActionView = secondarySideBtn + } else { + secondarybtn.visibility = View.VISIBLE + secondarySideBtn.visibility = View.GONE + secondaryActionView = secondarybtn + } + + primarybtn.apply { + isVisible = actionData?.primaryAction != null + text = actionData?.primaryAction?.title + actionData?.primaryAction?.titleColor?.let { setTextColor(it.parseColorSafe()) } + val primaryActionData = actionData?.primaryAction + setOnClickListener { + primaryActionData?.let { primaryActionData -> + if (primaryActionData.url != DISMISS) { + action?.invoke(primaryActionData) + } else if (selectedDate != null) { + var modifiedParameters = primaryActionData.parameters?.toMutableList() + modifiedParameters?.add( + LineItem(key = CALENDAR_DATE, value = selectedDate.toString()) + ) + ?: run { + modifiedParameters = + mutableListOf( + LineItem( + key = CALENDAR_DATE, + value = selectedDate.toString() + ) + ) + } + primaryActionData.parameters = modifiedParameters + action?.invoke(primaryActionData) + } else {} + } + safelyDismissDialog() + } + } + secondaryActionView.apply { + isVisible = actionData?.secondaryAction != null + text = actionData?.secondaryAction?.title + actionData?.secondaryAction?.titleColor?.let { setTextColor(it.parseColorSafe()) } + val secondaryActionData = actionData?.secondaryAction + setOnClickListener { + secondaryActionData?.let { action?.invoke(it) } + safelyDismissDialog() + } + } + } + } + + private fun initObservers() { + bottomSheetSharedVM.bottomSheetOpenData.observeNonNull(viewLifecycleOwner) { + bottomSheetVisible -> + if (bottomSheetVisible == false) { + safelyDismissDialog() + } + } + } + + override val screenName: String + get() = SubPageStatusType.AMC_DYNAMIC_BOTTOMSHEET + + companion object { + const val TAG = "AMC_DYNAMIC_BOTTOM_SHEET" + + @JvmStatic + fun newInstance( + bundle: Bundle, + subtitleListener: NewBottomSheetListener?, + action: ((ActionData) -> Unit)? = null, + prevSelectedDate: Int? = null + ): AmcDynamicBottomSheet { + return AmcDynamicBottomSheet().apply { + listener = subtitleListener + arguments = bundle + this.action = action + this.selectedDate = prevSelectedDate + } + } + } +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/fragment/OrderStatusFragment.kt b/android/navi-amc/src/main/java/com/navi/amc/common/fragment/OrderStatusFragment.kt index 1f4da030ad..c1dcad7a03 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/common/fragment/OrderStatusFragment.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/common/fragment/OrderStatusFragment.kt @@ -26,25 +26,30 @@ import com.navi.amc.common.listener.FooterInteractionListener import com.navi.amc.common.model.AdditionalDataAsyncResponse import com.navi.amc.common.model.AmcCommonBottomSheetData import com.navi.amc.common.model.NextCtaResponse +import com.navi.amc.common.model.SipOrderSummaryData import com.navi.amc.common.taskProcessor.AmcTaskManager import com.navi.amc.common.taskProcessor.OrdersPrefetchTask import com.navi.amc.common.viewmodel.OrderStatusViewModel import com.navi.amc.common.viewmodel.PaymentSharedVM import com.navi.amc.databinding.OrderStatusLayoutBinding +import com.navi.amc.fundbuy.models.AutoPaySetupRequestData import com.navi.amc.fundbuy.models.PaymentOrder import com.navi.amc.utils.AmcAnalytics import com.navi.amc.utils.AmcAnalytics.AMC_BTN_CSAT_PLAYSTORE_BSHEET_END import com.navi.amc.utils.AmcAnalytics.AMC_INIT_CSAT_PLAYSTORE_BSHEET import com.navi.amc.utils.AmcAnalytics.AMC_PAYMENT_TOKEN_RECEIVED +import com.navi.amc.utils.AmcAnalytics.AMC_RECEIVED_NULL_POST_PAYMENT_DATA import com.navi.amc.utils.AmcAnalytics.ORDER_STATUS_SCREEN import com.navi.amc.utils.AmcAnalytics.SCREEN_NAME import com.navi.amc.utils.Constant import com.navi.amc.utils.Constant.AMC_FIRST_NAVI_UPI_TXN import com.navi.amc.utils.Constant.AMOUNT +import com.navi.amc.utils.Constant.BANK_DETAILS_REF_ID import com.navi.amc.utils.Constant.DATA import com.navi.amc.utils.Constant.DATA_SOURCE import com.navi.amc.utils.Constant.DISMISS import com.navi.amc.utils.Constant.FUND_ID +import com.navi.amc.utils.Constant.GET_SIP_SUMMARY_BOTTOMSHEET import com.navi.amc.utils.Constant.HELP_BOTTOMSHEET import com.navi.amc.utils.Constant.INIT_REFUND import com.navi.amc.utils.Constant.NAVI_UPI_TXN @@ -54,9 +59,12 @@ import com.navi.amc.utils.Constant.ORDER_ID import com.navi.amc.utils.Constant.PAYMENT import com.navi.amc.utils.Constant.PAYMENT_ORDER_STATUS_POLLING import com.navi.amc.utils.Constant.PAYMENT_TIMESTAMP +import com.navi.amc.utils.Constant.REQUEST_CONFIG import com.navi.amc.utils.Constant.SHOW_BOTTOMSHEET +import com.navi.amc.utils.Constant.SIP_REFERENCE_ID import com.navi.amc.utils.Constant.THOUSAND import com.navi.amc.utils.Constant.THREE +import com.navi.amc.utils.Constant.TRANSACTION_ID import com.navi.amc.utils.Constant.TURBO_CHECKOUT_FLOW import com.navi.amc.utils.Constant.WHITE import com.navi.amc.utils.SubPageStatusType @@ -84,9 +92,11 @@ import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper import com.navi.common.listeners.FragmentInterchangeListener import com.navi.common.listeners.HeaderInteractionListener import com.navi.common.listeners.NewBottomSheetListener +import com.navi.common.model.AmcBottomSheetData import com.navi.common.model.HelpBottomSheetData import com.navi.common.model.RequestConfig import com.navi.common.model.UploadDataAsyncResponse +import com.navi.common.ui.activity.BaseActivity import com.navi.common.upi.UPI_REQUEST_ID import com.navi.common.utils.ApiPollScheduler import com.navi.common.utils.CommonUtils.toMap @@ -98,6 +108,8 @@ import com.navi.common.utils.observeNonNull import com.navi.common.utils.toCtaData import com.navi.design.textview.model.NaviSpan import com.navi.design.textview.model.TextWithStyle +import com.navi.design.utils.dpToPx +import com.navi.design.utils.getNaviDrawable import com.navi.design.utils.parseColorSafe import com.navi.design.utils.setSpannableString import com.navi.naviwidgets.callbacks.WidgetCallback @@ -106,7 +118,9 @@ import com.navi.naviwidgets.extensions.showWhenDataIsAvailable import com.navi.naviwidgets.models.response.CSATResponse import com.navi.naviwidgets.models.response.CsatWidget import com.navi.naviwidgets.utils.LottieEnums +import com.navi.naviwidgets.widgets.InfoWithTimerWidgetLayout.Companion.COLOR_WHITE import com.navi.payment.listener.PaymentListener +import com.navi.payment.model.initiatesdk.PaymentPrefetchMethodRequest import com.navi.payment.utils.PaymentAnalytics import com.navi.payment.utils.PaymentAnalytics.IS_TOKEN_VALID import com.navi.payment.utils.PaymentAnalytics.SYNC @@ -133,6 +147,7 @@ class OrderStatusFragment : private val paymentVM: PaymentManager by activityViewModels() private val paymentSharedVM: PaymentSharedVM by activityViewModels() private val scratchCardSharedVm: ScratchCardSharedVm by activityViewModels() + private var sipReferenceId: String? = null private var apiPollScheduler: ApiPollScheduler? = null private var type: String = "" private val bundle = Bundle() @@ -143,6 +158,7 @@ class OrderStatusFragment : private var playStoreInAppRatingHelper: PlayStoreInAppRatingHelper? = null private var csatFlowJob: Job? = null + private var orderSummaryBottomSheetList: List? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -189,10 +205,10 @@ class OrderStatusFragment : } private fun fetchData(forceRefresh: Boolean = false, extraParams: Map? = null) { - val orderId = arguments?.getString(Constant.ORDER_ID).orEmpty() - val dataSource = arguments?.getString(Constant.DATA_SOURCE).orEmpty() - bundle.putString(Constant.ORDER_ID, orderId) - bundle.putString(Constant.DATA_SOURCE, dataSource) + val orderId = arguments?.getString(ORDER_ID).orEmpty() + val dataSource = arguments?.getString(DATA_SOURCE).orEmpty() + bundle.putString(ORDER_ID, orderId) + bundle.putString(DATA_SOURCE, dataSource) viewModel.sourceScreen = buyFlowVM.provideLatestGreenScreen() if (viewModel.isOrderStatusPolling.orFalse()) { val fundName = arguments?.getString(ORDER_HEADER_TITLE).orEmpty() @@ -238,7 +254,7 @@ class OrderStatusFragment : private fun createOrder() { showLoader() - val orderId = arguments?.getString(Constant.ORDER_ID).orEmpty() + val orderId = arguments?.getString(ORDER_ID).orEmpty() viewModel.createRedeemOrder(orderId) } @@ -269,6 +285,16 @@ class OrderStatusFragment : it.amcHeaderData?.bgColor.parseColorSafe(WHITE) it.orderDetails?.let { response = it } + it.inProgressWidget?.let { data -> + inProgressWidget.background = + getNaviDrawable( + cornerRadius = dpToPx(data.radius?.toIntOrNull() ?: 4).toInt(), + backgroundColor = data.bgColor.parseColorSafe(COLOR_WHITE) + ) + titleLeftImage.showWhenDataIsAvailable(data.leftIcon) + title.setSpannableString(data.title) + subtitle.setSpannableString(data.subtitle) + } ?: run { inProgressWidget.visibility = View.GONE } orderUpdate.setSpannableString(it.orderUpdate) orderUpdate.setOnClickListener { view -> it.orderUpdate @@ -408,6 +434,64 @@ class OrderStatusFragment : ) } + buyFlowVM.autoPayPaymentInitiateData.observe(viewLifecycleOwner) { + sendEvent( + AMC_PAYMENT_TOKEN_RECEIVED, + hashMapOf( + SYNC to it?.syncFlow?.orFalse().toString(), + IS_TOKEN_VALID to + it?.tokenDetails?.naviSdkToken.isNotNullAndNotEmpty().toString() + ) + ) + hideLoader() + if (it?.syncFlow.orFalse()) { + paymentSharedVM.isSyncFlow = true + if (it?.tokenDetails?.naviSdkToken.isNotNullAndNotEmpty()) { + paymentSharedVM.syncFlowPaymentInitStartTime = System.currentTimeMillis() + sendEvent(AmcAnalytics.AMC_INIT_PAYMENT) + paymentVM.clear() + paymentVM.initActivity(activity = requireActivity()) + paymentVM.getPaymentMethodsV2( + it?.tokenDetails?.naviSdkToken!!, + PaymentPrefetchMethodRequest() + ) + } + } + } + + paymentSharedVM.postPaymentStatus.observe(viewLifecycleOwner) { data -> + data?.let { + val url = + getPaymentSyncFlowStatusCta(CheckerActivity.AUTO_PAY_PAYMENT_CALLBACK_SYNC) + val bundle = + Bundle().apply { + putString( + TRANSACTION_ID, + buyFlowVM.autoPayPaymentInitiateData.value?.tokenDetails?.transactionId + ) + putString( + Constant.NAVI_SDK_TOKEN, + buyFlowVM.autoPayPaymentInitiateData.value?.tokenDetails?.naviSdkToken + ) + putParcelable( + REQUEST_CONFIG, + buyFlowVM.autoPayPaymentInitiateData.value?.requestConfig + ) + putString(SYNC, PaymentAnalytics.TRUE) + putString( + ORDER_ID, + buyFlowVM.autoPayPaymentInitiateData.value?.tokenDetails?.naviOrderId + ) + putString(SIP_REFERENCE_ID, sipReferenceId) + putString(PaymentAnalytics.PROVIDER, it?.provider) + putParcelable(Constant.PAYMENT_DATA, it) + putAll(arguments) + } + popThisFromBackStack() + fragmentInterchangeListener?.navigateToNextScreen(ActionData(url = url), bundle) + } ?: run { sendEvent(AMC_RECEIVED_NULL_POST_PAYMENT_DATA) } + } + paymentSharedVM.postPaymentStatus.observe(viewLifecycleOwner) { data -> data?.let { paymentVM.postPaymentStatus(it) @@ -423,10 +507,10 @@ class OrderStatusFragment : viewModel.paymentInitiateData.value?.requestConfig ) putString( - Constant.ORDER_ID, + ORDER_ID, viewModel.paymentInitiateData.value?.tokenDetails?.naviOrderId ) - putString(PaymentAnalytics.SYNC, PaymentAnalytics.TRUE) + putString(SYNC, PaymentAnalytics.TRUE) putString(PaymentAnalytics.PROVIDER, it.provider) } fragmentInterchangeListener?.navigateToNextScreen(ActionData(url = url), bundle) @@ -449,6 +533,18 @@ class OrderStatusFragment : scratchCardSharedVm.setScratchCardScreenData(it.data) showRewardLottieAnimation(it, actionData = viewModel.getRewardActionData()) } + + buyFlowVM.sipOrderSummaryResponse.observe(viewLifecycleOwner) { + updateLoadingState(false) + it?.let { + orderSummaryBottomSheetList = it.bottomSheetsData + handleMultiBottomSheetAction( + it.actionData, + orderSummaryBottomSheetList, + listener = { initiateAutoPayForSip(it) } + ) + } + } } private fun showRewardLottieAnimation( @@ -784,14 +880,16 @@ class OrderStatusFragment : action.parameters?.firstOrNull { it.key == Constant.PAYMENT_MODE }?.value.orEmpty() val isOtpUrl = action.url.orEmpty().endsWith(SubPageStatusType.OTP, true) + val isSipSummaryUrl = action.url.orEmpty().endsWith(GET_SIP_SUMMARY_BOTTOMSHEET, true) if (isRetry && !isOtpUrl) { showLoader() viewModel.postSameOrderPayment( PaymentOrder(orderId = arguments?.getString(ORDER_ID), paymentMode = paymentMode) ) + } else if (isSipSummaryUrl) { + handleOnClick(action) } else { - val bundle = Bundle().apply { putString(ORDER_ID, arguments?.getString(ORDER_ID)) } fragmentInterchangeListener?.navigateToNextScreen(action, bundle) } @@ -836,7 +934,7 @@ class OrderStatusFragment : ) if (action?.type == DISMISS) return val url = action?.url - if (url == Constant.SHOW_BOTTOMSHEET) { + if (url == SHOW_BOTTOMSHEET) { val data = action.parameters?.getOrNull(0)?.value val key = action.parameters?.getOrNull(0)?.key val bundle = Bundle().apply { putString(Constant.DATA, data) } @@ -850,11 +948,56 @@ class OrderStatusFragment : initiateRefund(action) } else if (url == ACTION_TYPE_FORCE_PAGE_REFRESH) { fetchData(forceRefresh = true, extraParams = action.parameters?.toMap()) + } else if (url == GET_SIP_SUMMARY_BOTTOMSHEET) { + val sipOrderSummary = + SipOrderSummaryData( + screenName = screenName, + orderId = arguments?.getString(ORDER_ID).orEmpty() + ) + updateLoadingState(true) + buyFlowVM.getSipOrderSummaryData( + sipOrderSummaryData = sipOrderSummary, + screenName = screenName + ) } else { fragmentInterchangeListener?.navigateToNextScreen(actionData = action) } } + private fun updateLoadingState(isLoading: Boolean) { + binding.footerView.updateButtonLoaderState(isLoading) + if (isLoading) { + (activity as? BaseActivity)?.blockInteractability() + } else { + (activity as? BaseActivity)?.unblockInteractability() + } + } + + private fun initiateAutoPayForSip(action: ActionData?) { + if (action?.url == DISMISS) { + // do nothing + return + } + val nextCtaUrl = action?.url.orEmpty() + val amount = action?.parameters?.firstOrNull { it.key == AMOUNT }?.value.orEmpty() + val paymentMode = buyFlowVM.paymentMode.value + val bankDetailsRefId = + action?.parameters?.firstOrNull { it.key == BANK_DETAILS_REF_ID }?.value + sipReferenceId = action?.parameters?.firstOrNull { it.key == SIP_REFERENCE_ID }?.value + + val autoPaySetupRequestData = + AutoPaySetupRequestData( + bankAccountRefId = bankDetailsRefId, + mandateType = paymentMode, + amount = amount, + sipReferenceId = sipReferenceId + ) + buyFlowVM.postAutoPaySetupRequestDataV2( + autoPaySetupRequestData = autoPaySetupRequestData, + screenName = screenName + ) + } + override fun onBackPressed(actionData: ActionData?): Boolean { return showExitBottomSheetIfAvailable() } @@ -866,7 +1009,7 @@ class OrderStatusFragment : private fun showExitBottomSheetIfAvailable(): Boolean { if (!viewModel.isBackAllowed) { val exitCta = viewModel.orderStatusScreenData.value?.content?.exitBottomSheetCta - if (exitCta?.url?.contains(Constant.SHOW_BOTTOMSHEET).orFalse()) { + if (exitCta?.url?.contains(SHOW_BOTTOMSHEET).orFalse()) { viewModel.isBackAllowed = true val data = exitCta?.parameters?.get(0)?.value val key = exitCta?.parameters?.get(0)?.key diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/fragment/OtpFragment.kt b/android/navi-amc/src/main/java/com/navi/amc/common/fragment/OtpFragment.kt index 9488676b4a..762c297f1d 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/common/fragment/OtpFragment.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/common/fragment/OtpFragment.kt @@ -31,11 +31,14 @@ import com.navi.amc.common.taskProcessor.AmcTaskManager import com.navi.amc.common.viewmodel.OTPVM import com.navi.amc.common.viewmodel.PaymentSharedVM import com.navi.amc.databinding.OtpFragmentAmcBinding +import com.navi.amc.fundbuy.models.AutoPaySetupRequestData +import com.navi.amc.fundbuy.models.MandateWithSipRequestData import com.navi.amc.fundbuy.models.PaymentOrder import com.navi.amc.fundbuy.models.PaymentPostData import com.navi.amc.fundbuy.models.SipDetailsData import com.navi.amc.fundbuy.models.SipDetailsResponse import com.navi.amc.utils.AmcAnalytics +import com.navi.amc.utils.AmcAnalytics.AMC_RECEIVED_NULL_POST_PAYMENT_DATA import com.navi.amc.utils.AmcAnalytics.ISIN import com.navi.amc.utils.Constant import com.navi.amc.utils.Constant.AMOUNT @@ -60,12 +63,17 @@ import com.navi.amc.utils.Constant.PAYMENT_MODE import com.navi.amc.utils.Constant.REDEMPTION_ORDER_ID import com.navi.amc.utils.Constant.REQUEST_CONFIG import com.navi.amc.utils.Constant.SECONDS_PER_MINUTE +import com.navi.amc.utils.Constant.SETUP_AUTOPAY_ALL_SIP +import com.navi.amc.utils.Constant.SETUP_AUTOPAY_EXISTING_SIP +import com.navi.amc.utils.Constant.SETUP_SIP_AUTOPAY import com.navi.amc.utils.Constant.SIP_AUTOPAY_PRESENT import com.navi.amc.utils.Constant.SIP_DATE +import com.navi.amc.utils.Constant.SIP_NEXT_INSTALLMENT_DATE import com.navi.amc.utils.Constant.SIP_REFERENCE_ID import com.navi.amc.utils.Constant.SOURCE_REF_ID import com.navi.amc.utils.Constant.SOURCE_TYPE import com.navi.amc.utils.Constant.TRANSACTION_ID +import com.navi.amc.utils.Constant.TRANSACTION_REFERENCE_ID import com.navi.amc.utils.TempStorageHelper import com.navi.amc.utils.bundleToMap import com.navi.amc.utils.getPaymentSyncFlowStatusCta @@ -337,6 +345,31 @@ class OtpFragment : AmcBaseFragment(), View.OnClickListener { } } + viewModel.autoPayPaymentInitiateData.observe(viewLifecycleOwner) { + sendEvent( + AmcAnalytics.AMC_PAYMENT_TOKEN_RECEIVED, + hashMapOf( + PaymentAnalytics.SYNC to it?.syncFlow?.orFalse().toString(), + PaymentAnalytics.IS_TOKEN_VALID to + it?.tokenDetails?.naviSdkToken.isNotNullAndNotEmpty().toString() + ) + ) + hideLoader() + if (it?.syncFlow.orFalse()) { + paymentSharedVM.isSyncFlow = true + if (it?.tokenDetails?.naviSdkToken.isNotNullAndNotEmpty()) { + paymentSharedVM.syncFlowPaymentInitStartTime = System.currentTimeMillis() + sendEvent(AmcAnalytics.AMC_INIT_PAYMENT) + paymentVM.clear() + paymentVM.initActivity(activity = requireActivity()) + paymentVM.getPaymentMethodsV2( + it?.tokenDetails?.naviSdkToken!!, + PaymentPrefetchMethodRequest() + ) + } + } + } + paymentVM.paymentAnalyticsEvent.observe(viewLifecycleOwner) { val attributes = hashMapOf().apply { @@ -370,31 +403,76 @@ class OtpFragment : AmcBaseFragment(), View.OnClickListener { paymentSharedVM.postPaymentStatus.observe(viewLifecycleOwner) { data -> data?.let { - paymentVM.postPaymentStatus(it) - val url = getPaymentSyncFlowStatusCta(CheckerActivity.PAYMENT_CALLBACK_SYNC) - val bundle = - Bundle().apply { - putAll(arguments) - putString(SOURCE_TYPE, PAYMENT) - putString( - TRANSACTION_ID, - viewModel.paymentInitiateData.value?.tokenDetails?.transactionId - ) - putParcelable( - REQUEST_CONFIG, - viewModel.paymentInitiateData.value?.requestConfig - ) - putString( - ORDER_ID, - viewModel.paymentInitiateData.value?.tokenDetails?.naviOrderId - ) - putString(SIP_REFERENCE_ID, viewModel.sipReferenceId) - putString(PaymentAnalytics.SYNC, PaymentAnalytics.TRUE) - putString(PaymentAnalytics.PROVIDER, it.provider) - } - popThisFromBackStack() - fragmentInterchangeListener?.navigateToNextScreen(ActionData(url = url), bundle) - } + if ( + flowType in + listOf(SETUP_SIP_AUTOPAY, SETUP_AUTOPAY_ALL_SIP, SETUP_AUTOPAY_EXISTING_SIP) + ) { + val url = + getPaymentSyncFlowStatusCta(CheckerActivity.AUTO_PAY_PAYMENT_CALLBACK_SYNC) + val bundle = + Bundle().apply { + putString( + TRANSACTION_ID, + viewModel.autoPayPaymentInitiateData.value + ?.tokenDetails + ?.transactionId + ) // actually this is mandate reference id tb changed in future with be + putString( + Constant.NAVI_SDK_TOKEN, + viewModel.autoPayPaymentInitiateData.value + ?.tokenDetails + ?.naviSdkToken + ) + putParcelable( + REQUEST_CONFIG, + viewModel.autoPayPaymentInitiateData.value?.requestConfig + ) + putString(PaymentAnalytics.SYNC, PaymentAnalytics.TRUE) + putString( + ORDER_ID, + viewModel.autoPayPaymentInitiateData.value + ?.tokenDetails + ?.naviOrderId + ) + putString( + TRANSACTION_REFERENCE_ID, + viewModel.autoPayPaymentInitiateData.value + ?.tokenDetails + ?.tranReferenceId + ) + putString(SIP_REFERENCE_ID, viewModel.sipReferenceId) + putString(PaymentAnalytics.PROVIDER, it?.provider) + putParcelable(Constant.PAYMENT_DATA, it) + putAll(arguments) + } + popThisFromBackStack() + fragmentInterchangeListener?.navigateToNextScreen(ActionData(url = url), bundle) + } else { + val url = getPaymentSyncFlowStatusCta(CheckerActivity.PAYMENT_CALLBACK_SYNC) + val bundle = + Bundle().apply { + putAll(arguments) + putString(SOURCE_TYPE, PAYMENT) + putString( + TRANSACTION_ID, + viewModel.paymentInitiateData.value?.tokenDetails?.transactionId + ) + putParcelable( + REQUEST_CONFIG, + viewModel.paymentInitiateData.value?.requestConfig + ) + putString( + ORDER_ID, + viewModel.paymentInitiateData.value?.tokenDetails?.naviOrderId + ) + putString(SIP_REFERENCE_ID, viewModel.sipReferenceId) + putString(PaymentAnalytics.SYNC, PaymentAnalytics.TRUE) + putString(PaymentAnalytics.PROVIDER, it?.provider) + } + popThisFromBackStack() + fragmentInterchangeListener?.navigateToNextScreen(ActionData(url = url), bundle) + } + } ?: run { sendEvent(AMC_RECEIVED_NULL_POST_PAYMENT_DATA) } } viewModel.sipCreateResponse.observe(viewLifecycleOwner) { response -> @@ -458,9 +536,10 @@ class OtpFragment : AmcBaseFragment(), View.OnClickListener { ) } SIP_AUTOPAY_PRESENT -> { + // case when amount is less than mandate limit viewModel.createSip( SipDetailsData( - scheme = arguments?.getString(AmcAnalytics.ISIN), + scheme = arguments?.getString(ISIN), amount = arguments?.getString(AMOUNT), frequency = arguments?.getString(FREQUENCY), sipDate = arguments?.getString(SIP_DATE), @@ -472,15 +551,38 @@ class OtpFragment : AmcBaseFragment(), View.OnClickListener { OTP_FLOW_TYPE_SIP_MANUAL -> { val sipDetailsData = SipDetailsData( - scheme = arguments?.getString(AmcAnalytics.ISIN), + scheme = arguments?.getString(ISIN), amount = arguments?.getString(AMOUNT), frequency = arguments?.getString(FREQUENCY), sipDate = arguments?.getString(SIP_DATE), - paymentMode = arguments?.getString(Constant.PAYMENT_MODE), + paymentMode = arguments?.getString(PAYMENT_MODE), deletedSipReferenceId = arguments?.getString(DELETED_SIP_REFERENCE_ID) ) viewModel.createManualSip(sipDetailsData) } + SETUP_SIP_AUTOPAY -> { + // For creating new mandates with SIP this will be the flow type + val mandateWithSipRequestData = + MandateWithSipRequestData( + AutoPaySetupRequestData( + bankAccountRefId = arguments?.getString(Constant.BANK_DETAILS_REF_ID), + mandateType = arguments?.getString(PAYMENT_MODE), + amount = arguments?.getString(AMOUNT) + ), + SipDetailsData( + scheme = arguments?.getString(ISIN), + amount = arguments?.getString(AMOUNT), + frequency = arguments?.getString(FREQUENCY), + sipDate = arguments?.getString(SIP_DATE), + deletedSipReferenceId = arguments?.getString(DELETED_SIP_REFERENCE_ID), + sipNextInstallmentDate = arguments?.getString(SIP_NEXT_INSTALLMENT_DATE) + ) + ) + viewModel.createMandateWithSip( + mandateWithSipRequestData = mandateWithSipRequestData, + screenName = screenName + ) + } } } @@ -491,7 +593,7 @@ class OtpFragment : AmcBaseFragment(), View.OnClickListener { val sipDetails = SipDetailsData( - scheme = arguments?.getString(AmcAnalytics.ISIN), + scheme = arguments?.getString(ISIN), amount = arguments?.getString(AMOUNT), frequency = arguments?.getString(FREQUENCY), sipDate = sipDate, @@ -511,7 +613,7 @@ class OtpFragment : AmcBaseFragment(), View.OnClickListener { SipDetailsResponse( sipReferenceId = sipReferenceId, amount = arguments?.getString(AMOUNT), - paymentMode = arguments?.getString(Constant.PAYMENT_MODE) + paymentMode = arguments?.getString(PAYMENT_MODE) ) ) } else { @@ -532,8 +634,8 @@ class OtpFragment : AmcBaseFragment(), View.OnClickListener { viewModel.initiateLumpsumPayment( PaymentPostData( amount = arguments?.getString(AMOUNT)?.toDoubleOrNull(), - scheme = arguments?.getString(AmcAnalytics.ISIN), - paymentMode = arguments?.getString(Constant.PAYMENT_MODE) + scheme = arguments?.getString(ISIN), + paymentMode = arguments?.getString(PAYMENT_MODE) ), screenName = screenName ) @@ -569,7 +671,7 @@ class OtpFragment : AmcBaseFragment(), View.OnClickListener { viewModel.postSameOrderPayment( PaymentOrder( orderId = arguments?.getString(ORDER_ID), - paymentMode = arguments?.getString(Constant.PAYMENT_MODE).orEmpty() + paymentMode = arguments?.getString(PAYMENT_MODE).orEmpty() ) ) } @@ -577,7 +679,7 @@ class OtpFragment : AmcBaseFragment(), View.OnClickListener { private fun initData() { this.flowType = arguments?.getString(OTP_FLOW_TYPE).orEmpty() val fundIdParam = arguments?.getString(AmcAnalytics.FUND_ID) - val isinParam = arguments?.getString(AmcAnalytics.ISIN) + val isinParam = arguments?.getString(ISIN) if (isinParam.isNotNullAndNotEmpty()) { this.isin = isinParam diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/model/AdditionalDataAsyncResponse.kt b/android/navi-amc/src/main/java/com/navi/amc/common/model/AdditionalDataAsyncResponse.kt index ebaa0c885c..243e61d59b 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/common/model/AdditionalDataAsyncResponse.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/common/model/AdditionalDataAsyncResponse.kt @@ -8,6 +8,7 @@ package com.navi.amc.common.model import com.google.gson.annotations.SerializedName +import com.navi.amc.fundbuy.models.SipDetailsResponse import com.navi.common.model.UploadDataAsyncResponse data class AdditionalDataAsyncResponse( @@ -15,5 +16,6 @@ data class AdditionalDataAsyncResponse( @SerializedName("isTurboCheckoutFlow") val isTurboCheckoutFlow: Boolean = true, @SerializedName("tokenDetails", alternate = ["tokenData"]) val tokenDetails: TokenDetail? = null, + @SerializedName("createSipResponse") val createSipResponse: SipDetailsResponse? = null, @SerializedName("details", alternate = ["data"]) val data: T? ) : UploadDataAsyncResponse() diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/model/AmcCommonBottomSheetData.kt b/android/navi-amc/src/main/java/com/navi/amc/common/model/AmcCommonBottomSheetData.kt index a76071fa67..4087e770ab 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/common/model/AmcCommonBottomSheetData.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/common/model/AmcCommonBottomSheetData.kt @@ -12,6 +12,7 @@ import com.google.gson.annotations.SerializedName import com.navi.base.model.ActionData import com.navi.base.model.GenericAnalytics import com.navi.design.textview.model.TextWithStyle +import com.navi.naviwidgets.models.CalendarWidget import kotlinx.parcelize.Parcelize @Parcelize @@ -25,11 +26,14 @@ data class AmcCommonBottomSheetData( @SerializedName("metaData") val metaData: GenericAnalytics? = null, @SerializedName("horizontalActions") val horizontalActions: Boolean = false, @SerializedName("note") val note: AmcCommonBsheetNoteData? = null, - @SerializedName("imageUrl") val imageUrl: String? = null + @SerializedName("imageUrl") val imageUrl: String? = null, + @SerializedName("calendarData") val calendarData: CalendarWidget? = null ) : Parcelable @Parcelize data class AmcCommonBsheetNoteData( @SerializedName("title") val title: TextWithStyle? = null, - @SerializedName("bgColor") val bgColor: String? = null + @SerializedName("bgColor") val bgColor: String? = null, + @SerializedName("leftIconCode") val leftIconCode: String? = null, + @SerializedName("rightIconCode") val rightIconCode: String? = null ) : Parcelable diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/model/InformationCardData.kt b/android/navi-amc/src/main/java/com/navi/amc/common/model/InformationCardData.kt index a769198a50..b92a9c6887 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/common/model/InformationCardData.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/common/model/InformationCardData.kt @@ -13,7 +13,7 @@ import com.navi.design.textview.model.TextWithStyle import com.navi.naviwidgets.models.response.CardType data class InformationCardData( - @SerializedName("leftIcon") val leftIcon: String? = null, + @SerializedName("leftIcon", alternate = ["leftIconCode"]) val leftIcon: String? = null, @SerializedName("leftIconVerticalBias") val leftIconVerticalBias: String? = null, @SerializedName("rightIcon") val rightIcon: String? = null, @SerializedName("rightIconVerticalBias") val rightIconVerticalBias: String? = null, @@ -21,7 +21,7 @@ data class InformationCardData( @SerializedName("title") val title: TextWithStyle? = null, @SerializedName("subtitle", alternate = ["subTitle"]) val subtitle: TextWithStyle? = null, @SerializedName("bgColor") val bgColor: String? = null, - @SerializedName("bgRadius") val radius: String? = null, + @SerializedName("bgRadius", alternate = ["backgroundRadius"]) val radius: String? = null, @SerializedName("enableShimmer") val enableShimmer: Boolean? = null, @SerializedName("actionData") val actionData: ActionData? = null, var dismissed: Boolean = false diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/model/OrderStatusScreenData.kt b/android/navi-amc/src/main/java/com/navi/amc/common/model/OrderStatusScreenData.kt index 0bb086c7ed..762f1800f2 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/common/model/OrderStatusScreenData.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/common/model/OrderStatusScreenData.kt @@ -26,6 +26,7 @@ data class OrderStatusScreenData( data class OrderStatusContent( @SerializedName("fundHeader") val amcHeaderData: AmcHeaderData? = null, @SerializedName("orderDetails") val orderDetails: OrderDetailsData? = null, + @SerializedName("inProgressWidget") val inProgressWidget: InformationCardData? = null, @SerializedName("orderStatus") val orderStatus: StatusTimeLineData? = null, @SerializedName("orderInformation") val orderInformation: FundSummaryData? = null, @SerializedName("statusNote") val statusNote: InformationCardData? = null, diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/model/PaymentRequest.kt b/android/navi-amc/src/main/java/com/navi/amc/common/model/PaymentRequest.kt index bab45fbbef..3b85533295 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/common/model/PaymentRequest.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/common/model/PaymentRequest.kt @@ -42,4 +42,5 @@ data class TokenDetail( @SerializedName("transactionId") var transactionId: String? = null, @SerializedName("tokenExpiry") val tokenExpiry: Long? = null, @SerializedName("naviOrderId") val naviOrderId: String? = null, + @SerializedName("tranReferenceId") val tranReferenceId: String? = null ) diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/model/SipOrderSummaryData.kt b/android/navi-amc/src/main/java/com/navi/amc/common/model/SipOrderSummaryData.kt new file mode 100644 index 0000000000..a8485a8b0b --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/common/model/SipOrderSummaryData.kt @@ -0,0 +1,25 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.common.model + +import android.os.Parcelable +import com.google.gson.annotations.SerializedName +import kotlinx.parcelize.Parcelize + +@Parcelize +data class SipOrderSummaryData( + @SerializedName("isin") val isin: String? = null, + @SerializedName("amount") val amount: String? = null, + @SerializedName("frequency") val frequency: String? = null, + @SerializedName("sipDateId") val sipDateId: String? = null, + @SerializedName("paymentMode") val paymentMode: String? = null, + @SerializedName("screenName") val screenName: String? = null, + @SerializedName("sipReferenceId") val sipReferenceId: String? = null, + @SerializedName("orderId") val orderId: String? = null, + @SerializedName("sipEditNextInstallmentDate") val sipEditNextInstallmentDate: String? = null, +) : Parcelable diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/repo/CheckerRepository.kt b/android/navi-amc/src/main/java/com/navi/amc/common/repo/CheckerRepository.kt index dd9806c27a..c9d1f9c557 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/common/repo/CheckerRepository.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/common/repo/CheckerRepository.kt @@ -75,10 +75,18 @@ class CheckerRepository @Inject constructor(private val retrofitService: Retrofi suspend fun fetchAsyncRequestAutoPayDataPayment( requestId: String, - autopayType: String? = null + autopayType: String? = null, + tranReferenceId: String? = null, + metricInfo: MetricInfo>>? = null ) = apiResponseCallback( - retrofitService.fetchAsyncRequestAutoPayDataPayment(requestId, autopayType) + response = + retrofitService.fetchAsyncRequestAutoPayDataPayment( + requestId, + autopayType, + tranReferenceId + ), + metricInfo = metricInfo ) suspend fun fetchEsignAsyncRequestDataWithNextCta(requestId: String) = diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/repo/OTPRepository.kt b/android/navi-amc/src/main/java/com/navi/amc/common/repo/OTPRepository.kt index e5cc086b7b..dd2fab9f8c 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/common/repo/OTPRepository.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/common/repo/OTPRepository.kt @@ -16,6 +16,7 @@ import com.navi.amc.common.model.OtpGenerateResponse import com.navi.amc.common.model.OtpResponse import com.navi.amc.common.model.OtpResponseData import com.navi.amc.common.model.OtpVerificationBody +import com.navi.amc.fundbuy.models.MandateWithSipRequestData import com.navi.amc.fundbuy.models.PaymentOrder import com.navi.amc.fundbuy.models.PaymentPostData import com.navi.amc.fundbuy.models.SipDetailsData @@ -92,4 +93,13 @@ class OTPRepository @Inject constructor(private val retrofitService: RetrofitSer apiResponseCallback(retrofitService.initiateSipPayment(sipDetails)) suspend fun fetchSipSuccessPage() = apiResponseCallback(retrofitService.fetchSipSuccessPage()) + + suspend fun postMandateWithSipDetails( + details: MandateWithSipRequestData, + metricInfo: MetricInfo>>? = null + ) = + apiResponseCallback( + response = retrofitService.postMandateWithSipData(details), + metricInfo = metricInfo + ) } diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/view/FooterView.kt b/android/navi-amc/src/main/java/com/navi/amc/common/view/FooterView.kt index 6c42cef3ea..cfb6beb4db 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/common/view/FooterView.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/common/view/FooterView.kt @@ -12,6 +12,7 @@ import android.util.AttributeSet import android.view.LayoutInflater import android.view.View import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.databinding.DataBindingUtil import com.navi.amc.R @@ -21,16 +22,18 @@ import com.navi.amc.databinding.FooterLayoutBinding import com.navi.amc.utils.Constant.WHITE import com.navi.base.model.ActionData import com.navi.common.R as CommonR +import com.navi.design.utils.getNaviDrawable import com.navi.design.utils.parseColorSafe import com.navi.design.utils.setSpannableString import com.navi.naviwidgets.R as WidgetsR import com.navi.naviwidgets.extensions.showWhenDataIsAvailable -import com.navi.naviwidgets.utils.setButtonLoaderState +import com.navi.naviwidgets.utils.setButtonLoaderStateNonShrinking class FooterView(context: Context, attrs: AttributeSet?) : ConstraintLayout(context, attrs) { private var binding: FooterLayoutBinding private var footerInteractionListener: FooterInteractionListener? = null private var nextCtaText: String? = null + private var nextCtaColor: Int? = null init { val inflater = LayoutInflater.from(context) @@ -55,7 +58,10 @@ class FooterView(context: Context, attrs: AttributeSet?) : ConstraintLayout(cont nextCtaText = it binding.nextCta.text = it } - footer?.nextCta?.titleColor?.let { binding.nextCta.setTextColor(it.parseColorSafe()) } + footer?.nextCta?.titleColor?.let { + binding.nextCta.setTextColor(it.parseColorSafe()) + nextCtaColor = it.parseColorSafe() + } footer?.backCta?.title?.let { binding.backCta.text = it } footer?.backCta?.titleColor?.let { binding.backCta.setTextColor(it.parseColorSafe()) } binding.nextCta.setOnClickListener { @@ -161,7 +167,30 @@ class FooterView(context: Context, attrs: AttributeSet?) : ConstraintLayout(cont } fun updateButtonLoaderState(state: Boolean) { - nextCtaText?.let { setButtonLoaderState(binding.nextCta, binding.buttonLoader, state, it) } + nextCtaText?.let { + if (state) { + binding.nextCta.background = + getNaviDrawable( + cornerRadius = resources.getDimension(com.navi.design.R.dimen.dp_4).toInt(), + backgroundColor = + ContextCompat.getColor(context, R.color.button_loader_loading_color) + ) + } else { + binding.nextCta.background = + getNaviDrawable( + cornerRadius = resources.getDimension(com.navi.design.R.dimen.dp_4).toInt(), + backgroundColor = + ContextCompat.getColor(context, com.navi.design.R.color.color_1F002A) + ) + } + setButtonLoaderStateNonShrinking( + binding.nextCta, + binding.buttonLoader, + state, + it, + nextCtaColor + ) + } } enum class NextButtonTag { diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/viewmodel/CheckerVM.kt b/android/navi-amc/src/main/java/com/navi/amc/common/viewmodel/CheckerVM.kt index 91f6926323..311b4b5aad 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/common/viewmodel/CheckerVM.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/common/viewmodel/CheckerVM.kt @@ -250,7 +250,12 @@ class CheckerVM @Inject constructor(private val repository: CheckerRepository) : } } - fun checkApiPollStatus(requestId: String, type: String, screenName: String) { + fun checkApiPollStatus( + requestId: String, + type: String, + tranReferenceId: String? = null, + screenName: String + ) { coroutineScope.launch { when (type) { CheckerActivity.CVL_KYC_CHECK -> { @@ -305,7 +310,12 @@ class CheckerVM @Inject constructor(private val repository: CheckerRepository) : CheckerActivity.AUTO_PAY_PAYMENT_CALLBACK, CheckerActivity.AUTO_PAY_PAYMENT_CALLBACK_SYNC -> { val response = - repository.fetchAsyncRequestAutoPayDataPayment(requestId, autopayType) + repository.fetchAsyncRequestAutoPayDataPayment( + requestId, + autopayType, + tranReferenceId, + metricInfo = getAmcMetricInfo(screenName) + ) _asyncResponse.value = response } CheckerActivity.PL_DISBURSED_JOURNEY -> { diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/viewmodel/OTPVM.kt b/android/navi-amc/src/main/java/com/navi/amc/common/viewmodel/OTPVM.kt index 00adad3e37..1f26b47039 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/common/viewmodel/OTPVM.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/common/viewmodel/OTPVM.kt @@ -20,6 +20,7 @@ import com.navi.amc.common.model.OtpResponse import com.navi.amc.common.model.OtpResponseData import com.navi.amc.common.model.OtpVerificationBody import com.navi.amc.common.repo.OTPRepository +import com.navi.amc.fundbuy.models.MandateWithSipRequestData import com.navi.amc.fundbuy.models.PaymentOrder import com.navi.amc.fundbuy.models.PaymentPostData import com.navi.amc.fundbuy.models.SipDetailsData @@ -64,6 +65,11 @@ class OTPVM @Inject constructor(private val repository: OTPRepository) : BaseAmc val paymentInitiateData: LiveData?> get() = _paymentInitiateData + private val _autoPayPaymentInitiateData = + SingleLiveEvent?>() + val autoPayPaymentInitiateData: LiveData?> + get() = _autoPayPaymentInitiateData + private val _sipDetailsResponse = SingleLiveEvent() val sipDetailsResponse: LiveData get() = _sipDetailsResponse @@ -305,6 +311,25 @@ class OTPVM @Inject constructor(private val repository: OTPRepository) : BaseAmc } } + fun createMandateWithSip( + mandateWithSipRequestData: MandateWithSipRequestData, + screenName: String + ) { + viewModelScope.launch { + val response = + repository.postMandateWithSipDetails( + details = mandateWithSipRequestData, + metricInfo = getAmcMetricInfo(screenName = screenName) + ) + if (response.error == null && response.errors.isNullOrEmpty()) { + sipReferenceId = response.data?.createSipResponse?.sipReferenceId.orEmpty() + _autoPayPaymentInitiateData.value = updateCheckerResponse(response.data) + } else { + setErrorData(response.errors, response.error) + } + } + } + fun clearData() { errorResponse.value = null _sipDetailsResponse.value = null diff --git a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/fragments/AutoPaySuccessFragment.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/fragments/AutoPaySuccessFragment.kt index 19163ceded..7f2dadc100 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/fragments/AutoPaySuccessFragment.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/fragments/AutoPaySuccessFragment.kt @@ -79,6 +79,10 @@ class AutoPaySuccessFragment : AmcBaseFragment() { binding.nextBtn.isVisible = true binding.nextBtn.setText(it.title.orEmpty()) } + response?.footer?.backCta?.let { + binding.backBtn.isVisible = true + binding.backBtn.setText(it.title.orEmpty()) + } binding.titleTv.setSpannableString(response?.content?.title) binding.subTitleTv.setSpannableString(response?.content?.description) binding.bankNameTv.setSpannableString(response?.content?.bankNameTitle) @@ -137,10 +141,18 @@ class AutoPaySuccessFragment : AmcBaseFragment() { private fun initListeners() { binding.nextBtn.setOnClickListener { sendEvent(viewModel.dataItems.value?.footer?.nextCta?.metaData?.clickedData) + popThisFromBackStack() fragmentInterchangeListener?.navigateToNextScreen( viewModel.dataItems.value?.footer?.nextCta ) } + binding.backBtn.setOnClickListener { + sendEvent(viewModel.dataItems.value?.footer?.backCta?.metaData?.clickedData) + popThisFromBackStack() + fragmentInterchangeListener?.navigateToNextScreen( + viewModel.dataItems.value?.footer?.backCta + ) + } binding.autoPayTv.setOnClickListener { val bundle = Bundle().apply { diff --git a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/fragments/FundBuyingFragmentV2.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/fragments/FundBuyingFragmentV2.kt index aa1b4e2b4d..23f01fec45 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/fragments/FundBuyingFragmentV2.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/fragments/FundBuyingFragmentV2.kt @@ -233,6 +233,7 @@ class FundBuyingFragmentV2 : AmcBaseFragment(), WidgetCallback { if (viewModel.isAutoPayThere()) binding.autopayCheckbox.root.visibility = View.VISIBLE binding.lumpsumAmount.visibility = View.GONE binding.lumpsumRecommended.isVisible = false + binding.sipRecommended.refreshChipWidth(binding.sipRecommended) if ( !viewModel.isSipTutorialCrossed && viewModel.fundBuyScreenData.value?.content?.fundInvestmentType?.sipTutorial != null @@ -272,6 +273,7 @@ class FundBuyingFragmentV2 : AmcBaseFragment(), WidgetCallback { binding.lumpsumAmount.visibility = View.VISIBLE binding.sipRecommended.isVisible = false binding.lumpsumRecommended.isVisible = true + binding.lumpsumRecommended.refreshChipWidth(binding.lumpsumRecommended) viewModel.fundBuyScreenData.value?.content?.fundInvestmentType?.lumpsumPaymentMode?.let { binding.lumpsumPaymentCard.isVisible = true @@ -1185,6 +1187,7 @@ class FundBuyingFragmentV2 : AmcBaseFragment(), WidgetCallback { viewModel.sipType = item.id viewModel.setSelectedSipType(viewModel.sipType) binding.sipRecommended.isVisible = viewModel.toShowSipRecommended + binding.sipRecommended.refreshChipWidth(binding.sipRecommended) if (item.id == Constant.MONTHLY) { viewModel.toHideDate = false binding.date.visibility = View.VISIBLE diff --git a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/fragments/FundBuyingFragmentV3.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/fragments/FundBuyingFragmentV3.kt new file mode 100644 index 0000000000..a341afd26e --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/fragments/FundBuyingFragmentV3.kt @@ -0,0 +1,2185 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.fundbuy.fragments + +import android.content.Context +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.ViewStub +import android.view.WindowManager +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible +import androidx.databinding.DataBindingUtil +import androidx.fragment.app.activityViewModels +import androidx.fragment.app.viewModels +import com.navi.amc.R +import com.navi.amc.common.activity.CheckerActivity +import com.navi.amc.common.fragment.AmcBaseFragment +import com.navi.amc.common.fragment.AmcCommonBottomSheet +import com.navi.amc.common.fragment.AmcDynamicBottomSheet +import com.navi.amc.common.model.SipOrderSummaryData +import com.navi.amc.common.taskProcessor.AmcTaskManager +import com.navi.amc.common.viewmodel.AmcCommonBottomSheetVM +import com.navi.amc.common.viewmodel.PaymentSharedVM +import com.navi.amc.databinding.FundBuyingFragmentV3Binding +import com.navi.amc.fundbuy.models.AmountChipData +import com.navi.amc.fundbuy.models.AmountPageFooter +import com.navi.amc.fundbuy.models.AmountSimulationData +import com.navi.amc.fundbuy.models.FundBuyData +import com.navi.amc.fundbuy.models.GenericFooter +import com.navi.amc.fundbuy.models.InstallmentDateTypes +import com.navi.amc.fundbuy.models.LabeledAmountChipData +import com.navi.amc.fundbuy.models.PaymentPostData +import com.navi.amc.fundbuy.models.SipDetailsData +import com.navi.amc.fundbuy.viewmodel.FundBuyFlowViewModel +import com.navi.amc.fundbuy.viewmodel.FundBuyV2ViewModel +import com.navi.amc.utils.AmcAnalytics +import com.navi.amc.utils.AmcAnalytics.AMC_AMOUNT_PAGE_LUMPSUM_TAB_CLICKED +import com.navi.amc.utils.AmcAnalytics.AMC_AMOUNT_PAGE_SIP_TAB_CLICKED +import com.navi.amc.utils.Constant +import com.navi.amc.utils.Constant.ADD_AMOUNT +import com.navi.amc.utils.Constant.AMC +import com.navi.amc.utils.Constant.AMOUNT +import com.navi.amc.utils.Constant.AUTOPAY_CHECKED +import com.navi.amc.utils.Constant.BANK_DETAILS_REF_ID +import com.navi.amc.utils.Constant.CALENDAR_DATE +import com.navi.amc.utils.Constant.DAILY +import com.navi.amc.utils.Constant.DATA +import com.navi.amc.utils.Constant.DELETED_SIP_REFERENCE_ID +import com.navi.amc.utils.Constant.DISMISS +import com.navi.amc.utils.Constant.FIRST_INSTALLMENT_DATE +import com.navi.amc.utils.Constant.FREQUENCY +import com.navi.amc.utils.Constant.GET_SIP_SUMMARY_BOTTOMSHEET +import com.navi.amc.utils.Constant.INVESTMENT_TYPE +import com.navi.amc.utils.Constant.MONTHLY +import com.navi.amc.utils.Constant.NET_BANKING +import com.navi.amc.utils.Constant.NEXT_INSTALLMENT_DATE +import com.navi.amc.utils.Constant.ORDER_AMOUNT +import com.navi.amc.utils.Constant.PAYMENT_MODE +import com.navi.amc.utils.Constant.SHOW_BOTTOMSHEET +import com.navi.amc.utils.Constant.SIP_DATE +import com.navi.amc.utils.Constant.SIP_NEXT_INSTALLMENT_DATE +import com.navi.amc.utils.Constant.UPI +import com.navi.amc.utils.Constant.WEEKLY +import com.navi.amc.utils.Constant.WHITE +import com.navi.amc.utils.SubPageStatusType +import com.navi.amc.utils.TempStorageHelper +import com.navi.amc.utils.bundleToMap +import com.navi.amc.utils.getBottomSheet +import com.navi.amc.utils.getPaymentSyncFlowStatusCta +import com.navi.amc.utils.setNoteBannerData +import com.navi.amc.utils.showTimerInFormat +import com.navi.base.model.ActionData +import com.navi.base.model.LineItem +import com.navi.base.model.NaviClickAction +import com.navi.base.model.NaviWidgetClickWithActionData +import com.navi.base.utils.CurrencyUtils +import com.navi.base.utils.isNotNull +import com.navi.base.utils.isNotNullAndNotEmpty +import com.navi.base.utils.isNull +import com.navi.base.utils.orElse +import com.navi.base.utils.orFalse +import com.navi.base.utils.orZero +import com.navi.base.utils.toDoubleWithSafe +import com.navi.common.animation.slideInFromTop +import com.navi.common.animation.snapOut +import com.navi.common.listeners.FragmentInterchangeListener +import com.navi.common.listeners.HeaderInteractionListener +import com.navi.common.model.AmcBottomSheetData +import com.navi.common.pushnotification.NotificationConstants +import com.navi.common.ui.activity.BaseActivity +import com.navi.common.ui.fragment.SingleSelectionBottomSheet +import com.navi.common.utils.Constants.UNDERSCORE +import com.navi.common.utils.SPACE +import com.navi.common.utils.getDateSuffix +import com.navi.common.utils.observeNonNull +import com.navi.design.utils.CornerRadius +import com.navi.design.utils.dpToPx +import com.navi.design.utils.dpToPxInInt +import com.navi.design.utils.getNaviDrawable +import com.navi.design.utils.parseColorSafe +import com.navi.design.utils.setSpannableString +import com.navi.naviwidgets.base.InputWidgetModel +import com.navi.naviwidgets.callbacks.WidgetCallback +import com.navi.naviwidgets.databinding.RewardCalloutWidgetLayoutBinding +import com.navi.naviwidgets.databinding.TimerWithImageWidgetLayoutBinding +import com.navi.naviwidgets.extensions.showWhenDataIsAvailable +import com.navi.naviwidgets.models.RewardCalloutWidget +import com.navi.naviwidgets.models.RewardCalloutWidgetData +import com.navi.naviwidgets.models.SingleSelectionBottomSheetData +import com.navi.naviwidgets.utils.GenericTimer +import com.navi.naviwidgets.utils.LifecycleAwareCountdownTimer +import com.navi.naviwidgets.utils.TIMER_WITH_IMAGE_WIDGET +import com.navi.naviwidgets.utils.getGradientDrawable +import com.navi.naviwidgets.utils.setButtonLoaderState +import com.navi.naviwidgets.widgets.RewardCalloutWidgetLayout +import com.navi.naviwidgets.widgets.TimerWithImageWidgetLayout +import com.navi.naviwidgets.widgets.fixedhinttextinput.LabeledTextInputFixedHintWidgetModel +import com.navi.naviwidgets.widgets.textwithsearch.TextSearchClickAction +import com.navi.naviwidgets.widgets.textwithsearch.TextSearchWidgetModel +import com.navi.payment.listener.PaymentListener +import com.navi.payment.model.common.PaymentSdkInitParams +import com.navi.payment.model.initiatesdk.PaymentPrefetchMethodRequest +import com.navi.payment.nativepayment.sharedviewmodel.NaviCheckoutViewModel +import com.navi.payment.utils.Constants +import com.navi.payment.utils.PaymentAnalytics +import com.navi.payment.utils.PaymentSource +import com.navi.paymentclients.viewmodel.base.PaymentManager +import dagger.hilt.android.AndroidEntryPoint +import java.math.BigDecimal + +@AndroidEntryPoint +class FundBuyingFragmentV3 : AmcBaseFragment(), WidgetCallback { + + private lateinit var binding: FundBuyingFragmentV3Binding + private val viewModel by viewModels() + private val paymentVM: PaymentManager by activityViewModels() + private val paymentSharedVM: PaymentSharedVM by activityViewModels() + private val naviCheckoutViewModel by activityViewModels() + private val buyFlowViewModel: FundBuyFlowViewModel by activityViewModels() + private var confinedInvestmentType: String = AmcAnalytics.SIP_AND_LUMPSUM + private var isin: String? = null + private var sipSimulationFragmentData: AmountSimulationData? = null + private lateinit var rewardCalloutWidgetBinding: RewardCalloutWidgetLayoutBinding + private lateinit var timerWithImageWidgetLayoutBinding: TimerWithImageWidgetLayoutBinding + private val genericTimer by lazy { GenericTimer() } + private val bottomSheetSharedVM: AmcCommonBottomSheetVM by activityViewModels() + private var deletedSipReferenceId: String? = null + private var sipRecommendedAmounts: List? = null + private var firstInstallmentWeeklyBottomSheetData: SingleSelectionBottomSheetData? = null + private var nbInstallmentDates: InstallmentDateTypes? = null + private var upiInstallmentDates: InstallmentDateTypes? = null + private var sipNextCtaText: String? = null + private var orderSummaryBottomSheetList: List? = null + private var monthlySelectedDateId: Int? = null + + override val screenName: String + get() { + return when (confinedInvestmentType) { + AmcAnalytics.SIP -> AmcAnalytics.FUND_BUY_SIP_SCREEN + AmcAnalytics.LUMPSUMP -> AmcAnalytics.FUND_BUY_LUMPSUM_SCREEN + else -> AmcAnalytics.FUND_BUY_SIP_LUMPSUM_SCREEN + } + } + + override fun onAttach(context: Context) { + super.onAttach(context) + headerInteractionListener = context as? HeaderInteractionListener + fragmentInterchangeListener = context as? FragmentInterchangeListener + activity?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN) + } + + override fun onCreate(savedInstanceState: Bundle?) { + arguments?.let { bundle -> + confinedInvestmentType = bundle.getString(INVESTMENT_TYPE, AmcAnalytics.SIP_AND_LUMPSUM) + isin = bundle.getString(AmcAnalytics.ISIN) + buyFlowVM.fundId = isin + } + super.onCreate(savedInstanceState) + } + + override fun setContainerView(viewStub: ViewStub) { + viewStub.layoutResource = R.layout.fund_buying_fragment_v3 + binding = DataBindingUtil.getBinding(viewStub.inflate())!! + initError(viewModel) + initObservers() + } + + override fun onResume() { + super.onResume() + fetchScreenData(genericTimer.isInitialised()) + } + + override fun onStop() { + super.onStop() + genericTimer.stopTimer() + } + + private fun fetchScreenData(hardRefresh: Boolean? = false) { + if (hardRefresh == false) { + showLoader() + } + val orderAmount = arguments?.getString(ORDER_AMOUNT) + val defaultChecked = arguments?.getString(AmcAnalytics.SELECTED_TYPE) + deletedSipReferenceId = arguments?.getString(DELETED_SIP_REFERENCE_ID) + + isin?.let { + viewModel.getFundBuyScreenDataV2( + it, + confinedInvestmentType, + orderAmount, + defaultChecked, + screenName + ) + } + } + + private fun initUI(isLeftOptionSip: Boolean, userSelectedSipChoice: Boolean?) { + + var enableSipLeftOption = isLeftOptionSip + if (confinedInvestmentType == AmcAnalytics.SIP_AND_LUMPSUM) { + binding.switchView.isVisible = true + } else { + binding.switchView.isVisible = false + enableSipLeftOption = confinedInvestmentType == AmcAnalytics.SIP + } + val left = if (enableSipLeftOption) Constant.SIP else Constant.ONE_TIME + val right = if (enableSipLeftOption) Constant.ONE_TIME else Constant.SIP + + val defaultLeftChecked = + if (userSelectedSipChoice.isNull()) { + true + } else { + if (userSelectedSipChoice.orFalse()) { + enableSipLeftOption + } else { + enableSipLeftOption.not() + } + } + + binding.switchView.setProperties(left, right, defaultLeftChecked) { onLeftOptionSelected -> + val isSipSelectedBefore = viewModel.isSipSelected.orFalse() + viewModel.isSipSelected = + if (onLeftOptionSelected) { + left == Constant.SIP + } else right == Constant.SIP + + if (isSipSelectedBefore != viewModel.isSipSelected.orFalse()) { + if (viewModel.isSipSelected.orFalse()) { + sendEvent(AMC_AMOUNT_PAGE_SIP_TAB_CLICKED) + } else { + sendEvent(AMC_AMOUNT_PAGE_LUMPSUM_TAB_CLICKED) + } + } + + if (viewModel.isSipSelected.orFalse()) { + sipSelected() + } else { + lumpsumSelected() + } + + if (confinedInvestmentType != AmcAnalytics.SIP_AND_LUMPSUM) { + viewModel.isSipTutorialCrossed = true + viewModel.isLumpSumTutorialCrossed = true + binding.sipTutorial.root.isVisible = false + binding.lumpsumTutorial.root.isVisible = false + } + } + } + + private fun sipSelected() { + binding.sipAmount.visibility = View.VISIBLE + binding.sipRecommended.isVisible = viewModel.toShowSipRecommended + binding.sipRecommended.refreshChipWidth(binding.sipRecommended) + binding.sipFrequency.visibility = View.VISIBLE + if (viewModel.sipType.isNullOrEmpty().not()) { + if (!viewModel.toHideDate) binding.date.visibility = View.VISIBLE + } + if (viewModel.isAutoPayThere()) binding.autopayCheckbox.root.visibility = View.VISIBLE + binding.lumpsumAmount.visibility = View.GONE + binding.lumpsumRecommended.isVisible = false + if ( + !viewModel.isSipTutorialCrossed && + viewModel.fundBuyScreenData.value?.content?.fundInvestmentType?.sipTutorial != null + ) { + binding.sipTutorial.root.visibility = View.VISIBLE + } + binding.lumpsumTutorial.root.visibility = View.GONE + binding.lumpsumPaymentCard.isVisible = false + binding.cutoffBanner.isVisible = false + binding.lumpsumProjection.container.visibility = View.GONE + viewModel.isSipSelected = true + if ( + viewModel.isSipFundReturnNegative || + viewModel.isSipErrorState || + viewModel.sipType.isNullOrEmpty() + ) { + binding.sipProjection.container.visibility = View.GONE + } else { + binding.sipProjection.container.visibility = View.VISIBLE + } + if (viewModel.sipType == DAILY) { + binding.date.visibility = View.GONE + } + setFooter() + } + + private fun lumpsumSelected() { + if ( + !viewModel.isLumpSumTutorialCrossed && + viewModel.fundBuyScreenData.value?.content?.fundInvestmentType?.lumpsumTutorial != + null + ) { + binding.lumpsumTutorial.root.visibility = View.VISIBLE + } + binding.sipTutorial.root.visibility = View.GONE + if (viewModel.isAutoPayThere()) binding.autopayCheckbox.root.visibility = View.GONE + binding.sipFrequency.visibility = View.GONE + binding.date.visibility = View.GONE + binding.sipAmount.visibility = View.GONE + binding.lumpsumAmount.visibility = View.VISIBLE + binding.sipRecommended.isVisible = false + binding.lumpsumRecommended.isVisible = true + binding.sipRecommended.refreshChipWidth( + binding.lumpsumRecommended + ) // can add a counter to run this only for first iteration + + viewModel.fundBuyScreenData.value?.content?.fundInvestmentType?.lumpsumPaymentMode?.let { + binding.lumpsumPaymentCard.isVisible = true + } ?: run { binding.lumpsumPaymentCard.isVisible = false } + + binding.cutoffBanner.isVisible = true + viewModel.isSipSelected = false + if (viewModel.isFundReturnNegative || viewModel.isAmountErrorState) { + binding.lumpsumProjection.container.visibility = View.GONE + } else { + binding.lumpsumProjection.container.visibility = View.VISIBLE + } + binding.sipProjection.container.visibility = View.GONE + setFooter() + } + + private fun initObservers() { + + viewModel.fundBuyScreenData.observe(viewLifecycleOwner) { it -> + hideLoader() + + it?.header?.let { header -> + setHeaderProperties(header) + hideDivider() + } + + val userSelectedSipChoice = viewModel.isSipSelected + + it?.content?.let { content -> + buyFlowVM.fundName = content.fundHeaderData?.title?.text + binding.header.setProperties( + content.fundHeaderData, + { actionData -> + sendEvent( + actionData.metaData?.clickedData, + hashMapOf( + AmcAnalytics.FUND_ID to + arguments?.getString(AmcAnalytics.ISIN).orEmpty() + ) + ) + navigate(actionData) + } + ) + + viewModel.isSipSelected = + viewModel.isSipSelected + ?: content.fundInvestmentType?.defaultChecked.equals(Constant.SIP) + + if (viewModel.sipData.isNotNull()) { + content.fundInvestmentType?.sipData = viewModel.sipData + } + if (viewModel.lumpsumAmount.isNotNull()) { + content.fundInvestmentType?.lumpsumData = viewModel.lumpsumData + } + + content.fundInvestmentType?.simulationData?.let { + binding.lumpsumAmount.enableSuccessText = false + binding.lumpsumAmount.toggleSuccessStateVisibility(false) + } + + content.fundInvestmentType?.sipSimulationData?.let { + binding.sipAmount.enableSuccessText = false + binding.sipAmount.toggleSuccessStateVisibility(false) + } + + viewModel.sipData = content.fundInvestmentType?.sipData + viewModel.lumpsumData = content.fundInvestmentType?.lumpsumData + + content.maxAutoPayLimit?.let { maxMandateLimit -> + viewModel.maxMandateLimit = maxMandateLimit + } + + content.paymentModeLimit?.let { paymentModeLimit -> + viewModel.paymentModeLimit = paymentModeLimit + } + + content.fundInvestmentType?.installmentDates?.let { installmentDates -> + this.nbInstallmentDates = installmentDates.netBanking + this.upiInstallmentDates = installmentDates.upi + } + + content.fundInvestmentType?.weeklySipFirstInstallmentDayBottomSheet?.let { + bottomSheets -> + firstInstallmentWeeklyBottomSheetData = bottomSheets + } + + content.fundInvestmentType?.sipData?.getOrNull(0)?.let { sipFrequencyData -> + binding.sipFrequency.updateData(sipFrequencyData as InputWidgetModel, this, 0) + viewModel.sipType = sipFrequencyData.inputData + } + + content.fundInvestmentType?.sipData?.getOrNull(1)?.let { amount -> + binding.sipAmount.updateData(amount as InputWidgetModel, this) + binding.sipAmount.widgetBinding.plainTextInput.id = R.id.sip_amount + viewModel.sipAmount = amount.inputData + viewModel.checkPaymentModeNetBanking() + } + + content.fundInvestmentType?.sipData?.getOrNull(2)?.let { date -> + binding.date.toggleRightIconState(true) + binding.date.setDefaultShowCrossIcon(false) + binding.date.updateData(date as InputWidgetModel, 2, this) + binding.date.setLeftIcon(date.widgetData?.leftIconCode) + binding.date.widgetBinding.plainTextInput.id = R.id.date + if (viewModel.sipType == DAILY) { + binding.date.visibility = View.GONE + } else { + binding.date.setSavedTextVariations(id = viewModel.sipType) + viewModel.sipDateId = binding.date.setDateId(viewModel.sipType) + } + } + + autoFillSipData(content) + + content.fundInvestmentType?.lumpsumData?.getOrNull(0)?.let { lumpSum -> + binding.lumpsumAmount.updateData(lumpSum as InputWidgetModel, this) + viewModel.lumpsumAmount = lumpSum.inputData + } + + content.fundInvestmentType?.informationNote?.let { + binding.socialValidation.apply { + root.isVisible = true + icon.showWhenDataIsAvailable(it.iconCode) + title.setSpannableString(it.title) + subTitle.setSpannableString(it.subtitle) + root.background = + getNaviDrawable( + cornerRadius = dpToPxInInt(8), + backgroundColor = it.bgColor.parseColorSafe() + ) + } + } ?: run { binding.socialValidation.root.isVisible = false } + binding.lumpsumAmount.widgetBinding.plainTextInput.id = R.id.lumpsum_amount + binding.lumpsumAmount.addTextWatcher( + object : TextWatcher { + override fun beforeTextChanged( + p0: CharSequence?, + p1: Int, + p2: Int, + p3: Int + ) {} + + override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + viewModel.lastLumpsumInteraction = AmcAnalytics.TYPE + binding.lumpsumAmount.getUserInput()?.let { userInput -> + binding.lumpsumRecommended.setSelectedAmount(userInput) + val errorState = binding.lumpsumAmount.getUserInputPostValidation() + if (binding.paymentFooter.root.visibility == View.VISIBLE) { + binding.paymentFooter.amount.text = + CurrencyUtils.getNormalizedAmount( + if ( + errorState != null && + userInput.isNotNullAndNotEmpty() + ) + userInput.toBigDecimal() + else BigDecimal(0) + ) + } + viewModel.lumpsumAmount = userInput + if ( + errorState != null && + userInput.isNotNullAndNotEmpty() && + content.fundInvestmentType?.simulationData != null + ) { + viewModel.isAmountErrorState = false + if (viewModel.isSipSelected == false) { + viewModel + .projectedReturn(userInput, viewModel.isSipSelected) + .apply { + viewModel.projectedText?.let { projectedText -> + projectedText.text = this + binding.lumpsumProjection.projectionTitle + .setSpannableString(projectedText) + slideInFromTop( + binding.lumpsumProjection.container + ) {} + } + } + } + } else { + binding.lumpsumProjection.container.visibility = View.GONE + snapOut(binding.lumpsumProjection.container) {} + viewModel.isAmountErrorState = true + } + (content.fundInvestmentType?.lumpsumData?.getOrNull(0) + as? LabeledTextInputFixedHintWidgetModel) + ?.let { lumpSum -> + lumpSum.widgetData?.inputTextFixedHintItemData?.savedText = + userInput + } + } + } + + override fun afterTextChanged(p0: Editable?) {} + } + ) + content.fundInvestmentType?.simulationData?.let { simulationData -> + viewModel.lumpSumReturnRate = simulationData.lumpsumReturnRate + viewModel.isFundReturnNegative = simulationData.lumpsumReturnRate == null + binding.lumpsumProjection.image.showWhenDataIsAvailable(simulationData.iconCode) + binding.lumpsumProjection.title.setSpannableString( + simulationData.simulationText + ) + if ( + viewModel.lumpsumAmount.isNotNullAndNotEmpty() && + binding.lumpsumAmount.getUserInputPostValidation() != null + ) { + val defaultAmount = + viewModel.projectedReturn(viewModel.lumpsumAmount, false) + defaultAmount?.let { amount -> + viewModel.projectedText = simulationData.projectedReturnsText + viewModel.projectedText?.text = amount + binding.lumpsumProjection.projectionTitle.setSpannableString( + viewModel.projectedText + ) + if (viewModel.isSipSelected == false) { + binding.lumpsumProjection.container.isVisible = true + } + } + } + } + ?: run { + viewModel.isFundReturnNegative = true + binding.lumpsumAmount.enableSuccessText = true + binding.lumpsumAmount.toggleSuccessStateVisibility(false) + } + content.fundInvestmentType?.lumpsumRecommendedAmount?.let { chipData -> + setRecommendedPills(chipData, false) + } + + content.fundInvestmentType?.lumpsumPaymentMode?.let { paymentMode -> + paymentMode.header?.let { paymentHeader -> + binding.paymentModeIv.showWhenDataIsAvailable(paymentHeader.iconCode) + binding.paymentModeTv.setSpannableString(paymentHeader.title) + } + + paymentMode.content?.let { paymentContent -> + binding.bankAccountIv.showWhenDataIsAvailable(paymentContent.iconUrl) + binding.bankAccountInfoTv.setSpannableString(paymentContent.title) + } + + paymentMode.footer?.let { paymentFooter -> + binding.alternativePaymentsTv.setSpannableString(paymentFooter.title) { + actionData -> + if (binding.lumpsumAmount.getUserInputPostValidation() != null) { + + sendEvent( + actionData.metaData?.clickedData, + hashMapOf( + Pair( + AmcAnalytics.FUND_ID, + arguments?.getString(AmcAnalytics.ISIN).orEmpty() + ) + ) + ) + + val bundle = + createLumpSumPaymentBundle().apply { + if ( + confinedInvestmentType != AmcAnalytics.SIP_AND_LUMPSUM + ) { + putString( + Constant.CONFINED_INVESTMENT_TYPE, + confinedInvestmentType + ) + } + } + + fragmentInterchangeListener?.navigateToNextScreen( + actionData, + bundle + ) + } + } + } + } ?: run { binding.lumpsumPaymentCard.isVisible = false } + + binding.sipAmount.addTextWatcher( + object : TextWatcher { + override fun beforeTextChanged( + p0: CharSequence?, + p1: Int, + p2: Int, + p3: Int + ) {} + + override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + viewModel.lastSipInteraction = AmcAnalytics.TYPE + binding.sipAmount.getUserInput()?.let { sipUserInput -> + binding.sipRecommended.setSelectedAmount(sipUserInput) + val errorState = binding.sipAmount.getUserInputPostValidation() + viewModel.sipAmount = sipUserInput + viewModel.checkPaymentModeNetBanking() + if ( + errorState != null && + sipUserInput.isNotNullAndNotEmpty() && + content.fundInvestmentType?.sipSimulationData != null + ) { + viewModel.isSipErrorState = false + if ( + viewModel.isSipSelected == true && + viewModel.sipType.isNotNullAndNotEmpty() + ) { + viewModel + .projectedReturn( + sipUserInput, + viewModel.isSipSelected, + viewModel.sipType + ) + .apply { + viewModel.projectedText?.let { projectedText -> + projectedText.text = this + binding.sipProjection.projectionTitle + .setSpannableString(projectedText) + slideInFromTop( + binding.sipProjection.container + ) {} + } + } + } + } else { + binding.sipProjection.container.visibility = View.GONE + snapOut(binding.sipProjection.container) {} + viewModel.isSipErrorState = true + } + (content.fundInvestmentType?.sipData?.getOrNull(1) + as? LabeledTextInputFixedHintWidgetModel) + ?.let { sip -> + sip.widgetData?.inputTextFixedHintItemData?.savedText = + sipUserInput + } + } + } + + override fun afterTextChanged(p0: Editable?) {} + } + ) + content.fundInvestmentType?.sipSimulationData?.let { sipSimulationData -> + viewModel.dailySipReturnRate = sipSimulationData.dailySipReturnRate + viewModel.weeklySipReturnRate = sipSimulationData.weeklySipReturnRate + viewModel.monthlySipReturnRate = sipSimulationData.monthlySipReturnRate + viewModel.isSipFundReturnNegative = + when (viewModel.sipType) { + DAILY -> viewModel.dailySipReturnRate == null + WEEKLY -> viewModel.weeklySipReturnRate == null + MONTHLY -> viewModel.monthlySipReturnRate == null + else -> false + } + sipSimulationFragmentData = sipSimulationData + binding.sipProjection.image.showWhenDataIsAvailable(sipSimulationData.iconCode) + binding.sipProjection.title.setSpannableString(sipSimulationData.simulationText) + sipSimulationFragmentData?.let { simulationData -> + if ( + viewModel.sipAmount.isNotNullAndNotEmpty() && + binding.sipAmount.getUserInputPostValidation() != null + ) { + viewModel.isSipErrorState = false + val defaultAmount = + viewModel.projectedReturn( + viewModel.sipAmount, + viewModel.isSipSelected, + viewModel.sipType + ) + defaultAmount?.let { amount -> + viewModel.projectedText = simulationData.projectedReturnsText + viewModel.projectedText?.text = amount + binding.sipProjection.projectionTitle.setSpannableString( + viewModel.projectedText + ) + } + } + } + } + ?: run { + viewModel.isSipFundReturnNegative = true + binding.sipAmount.enableSuccessText = true + binding.sipAmount.toggleSuccessStateVisibility(false) + } + content.fundInvestmentType?.sipRecommendedAmounts?.let { recommendedAmounts -> + sipRecommendedAmounts = recommendedAmounts + recommendedAmounts + .firstOrNull() { it.id == viewModel.sipType } + ?.recommendedAmount + ?.let { chipData -> setRecommendedPills(chipData, true) } + } + ?: run { + content.fundInvestmentType?.sipRecommendedAmount?.let { chipData -> + setRecommendedPills(chipData, true) + } + } + + content.terms?.let { terms -> + binding.terms.setSpannableString(terms) { actionData -> + val data = + if (viewModel.isSipSelected.orFalse()) + actionData.parameters + ?.singleOrNull { pair -> pair.key == Constant.SIP } + ?.value + else + actionData.parameters + ?.singleOrNull { pair -> pair.key == Constant.LUMPSUM } + ?.value + val bundle = Bundle().apply { putString(DATA, data) } + val bottomSheet = CutOffTimeBankBottomSheet.newInstance(bundle) + safelyShowBottomSheet(bottomSheet, CutOffTimeBankBottomSheet.SCREEN) + } + } + + content.fundInvestmentType?.sipTutorial?.let { tutorial -> + binding.sipTutorial.title.setSpannableString(tutorial.title) + binding.sipTutorial.subtitle.setSpannableString(tutorial.subtitle) + binding.sipTutorial.cross.setOnClickListener { + viewModel.isSipTutorialCrossed = true + binding.sipTutorial.root.isVisible = false + } + } + + content.fundInvestmentType?.lumpsumTutorial?.let { tutorial -> + binding.lumpsumTutorial.root.background = + context?.let { ctx -> + ContextCompat.getDrawable(ctx, R.drawable.lumpsum_background) + } + binding.lumpsumTutorial.title.setSpannableString(tutorial.title) + binding.lumpsumTutorial.subtitle.setSpannableString(tutorial.subtitle) + binding.lumpsumTutorial.cross.setOnClickListener { + viewModel.isLumpSumTutorialCrossed = true + binding.lumpsumTutorial.root.isVisible = false + } + } + + content.fundInvestmentType?.autoPay?.let { autoPayData -> + binding.autopayCheckbox.root.isVisible = true + binding.autopayCheckbox.checkbox.isChecked = + viewModel.isAutoPaySelected ?: autoPayData.isChecked.orFalse() + viewModel.isAutoPaySelected = binding.autopayCheckbox.checkbox.isChecked + binding.autopayCheckbox.title.setSpannableString(autoPayData.title) + binding.autopayCheckbox.root.setOnClickListener { + binding.autopayCheckbox.checkbox.isChecked = + binding.autopayCheckbox.checkbox.isChecked.not() + viewModel.isAutoPaySelected = binding.autopayCheckbox.checkbox.isChecked + } + } + + binding.rewards.root.isVisible = + content.rewards?.let { rewards -> + binding.rewards.title.setSpannableString(rewards.title) + rewards.lottieFileName?.let { lottie -> + binding.rewards.lottie.showWhenDataIsAvailable(lottie) + } ?: run { binding.rewards.icon.showWhenDataIsAvailable(rewards.icon) } + if (rewards.label?.endDateTime.isNotNull()) { + binding.rewards.label.minWidth = dpToPxInInt(100) + LifecycleAwareCountdownTimer( + lifecycle, + rewards.label?.endDateTime?.toLong().orZero(), + { time -> + showTimerInFormat( + rewards.label, + time, + binding.rewards.label, + ::fetchScreenData + ) + }, + { time -> fetchScreenData() }, + true + ) + } else { + binding.rewards.label.setSpannableString(rewards.label?.title) + } + binding.rewards.label.background = + getNaviDrawable( + radii = CornerRadius(leftBottom = dpToPx(4)), + backgroundColor = rewards.label?.bgColor.parseColorSafe() + ) + + content.rewards.gradient?.let { + binding.rewards.root.background = getGradientDrawable(context, it) + } + ?: run { + binding.rewards.root.setBackgroundColor( + content.rewards.bgColor.parseColorSafe() + ) + } + + true + } ?: run { false } + + confinedInvestmentType.let { type -> + val eventParams = + hashMapOf( + AmcAnalytics.FUND_ID to isin.orEmpty(), + AmcAnalytics.ACTIVE_MANDATE to viewModel.isAutoPayThere().toString() + ) + + val eventName = + when (type) { + AmcAnalytics.LUMPSUMP -> AmcAnalytics.AMC_INIT_SIMPLIFIED_LUMPSUM + AmcAnalytics.SIP -> AmcAnalytics.AMC_INIT_SIMPLIFIED_SIP + else -> AmcAnalytics.AMC_INIT_OLD_SIP_LUMPSUM + } + + sendEvent(eventName, eventParams) + } + viewModel.lumpsumAmount?.let { binding.lumpsumRecommended.setSelectedAmount(it) } + viewModel.sipAmount?.let { binding.sipRecommended.setSelectedAmount(it) } + setNoteBannerData( + binding.noteBanner, + content.fundInvestmentType?.noteBanner, + sendEventData = ::sendEvent, + genericListener = ::handleOnClick, + showBottomSheet = ::safelyShowBottomSheet + ) + + content.cutOffData?.let { cutOffData -> setTimerWithImageWidgetData(cutOffData) } + + content.rewardCalloutWidgetData?.let { setRewardCalloutWidgetData(binding, it) } + } + + setFooter() + + initUI( + it?.content?.fundInvestmentType?.defaultChecked.equals(Constant.SIP), + userSelectedSipChoice + ) + } + + viewModel.paymentModeNetBanking.observe(viewLifecycleOwner) { + it?.let { + if (it) { + viewModel.paymentMode.value = NET_BANKING + } else { + viewModel.paymentMode.value = UPI + } + } + } + + buyFlowViewModel.paymentMode.observeNonNull(viewLifecycleOwner) { + if (buyFlowViewModel.paymentMode.value != viewModel.paymentMode.value) { + viewModel.paymentMode.value = buyFlowViewModel.paymentMode.value + } + } + + viewModel.selectedSipType.observe(viewLifecycleOwner) { + it.let { + sipSimulationFragmentData?.let { simulationData -> + if ( + viewModel.sipAmount.isNotNullAndNotEmpty() && + binding.sipAmount.getUserInputPostValidation() != null + ) { + viewModel.isSipErrorState = false + val defaultAmount = + viewModel.projectedReturn( + viewModel.sipAmount, + viewModel.isSipSelected, + viewModel.sipType + ) + defaultAmount?.let { amount -> + viewModel.projectedText = simulationData.projectedReturnsText + viewModel.projectedText?.text = amount + binding.sipProjection.projectionTitle.setSpannableString( + viewModel.projectedText + ) + if ( + viewModel.isSipSelected == true && + viewModel.sipType.isNotNullAndNotEmpty() + ) { + binding.sipProjection.container.isVisible = true + } + } + } + } + + sipRecommendedAmounts + ?.firstOrNull { it.id == viewModel.sipType } + ?.recommendedAmount + ?.let { chipData -> setRecommendedPills(chipData, true, true) } + + if (it == WEEKLY) { + viewModel.weeklyDefaultSipDateText?.let { weeklySavedText -> + binding.date.setText(weeklySavedText) + } + ?: run { + binding.date.setSavedTextVariations(it) + viewModel.sipDateId = binding.date.setDateId(it) + } + } else if (it == MONTHLY) { + viewModel.monthlyDefaultSipDateText?.let { monthlySavedText -> + binding.date.setText(monthlySavedText) + } + ?: run { + binding.date.setSavedTextVariations(it) + viewModel.sipDateId = binding.date.setDateId(it) + } + } + } + } + + viewModel.sipDetailsResponse.observe(viewLifecycleOwner) { + hideLoader() + it?.let { + AmcTaskManager.onPrefetchTaskRequired( + AmcTaskManager.SIP_PREFETCH_TASK, + buyFlowVM.provideLatestGreenScreen() + ) + val bundle = + Bundle().apply { + putString(AMOUNT, binding.sipAmount.getUserInput()) + putString(AmcAnalytics.ISIN, arguments?.getString(AmcAnalytics.ISIN)) + putString(Constant.TYPE, viewModel.getSipDisplayType()) + putString(Constant.SIP_REFERENCE_ID, it.sipReferenceId) + + if (confinedInvestmentType != AmcAnalytics.SIP_AND_LUMPSUM) { + putString(Constant.CONFINED_INVESTMENT_TYPE, confinedInvestmentType) + } + } + fragmentInterchangeListener?.navigateToNextScreen( + viewModel.getSelectedFooter()?.nextCta, + bundle + ) + } + } + + buyFlowViewModel.sipOrderSummaryResponse.observe(viewLifecycleOwner) { + updateLoadingState(false) + it?.let { + orderSummaryBottomSheetList = it.bottomSheetsData + handleMultiBottomSheetAction( + it.actionData, + orderSummaryBottomSheetList, + listener = { initiateSip(it) } + ) + } + } + + viewModel.paymentMode.observeNonNull(viewLifecycleOwner) { + buyFlowVM.paymentMode.value = it + binding.paymentFooter.leftIcon.showWhenDataIsAvailable(it) + if (it == "UPI") { + binding.paymentFooter.amount.text = "UPI" + } else { + binding.paymentFooter.amount.text = "Netbanking" + } + } + + viewModel.paymentInitiateData.observe(viewLifecycleOwner) { + sendEvent( + AmcAnalytics.AMC_PAYMENT_TOKEN_RECEIVED, + hashMapOf( + PaymentAnalytics.SYNC to it?.syncFlow?.orFalse().toString(), + PaymentAnalytics.IS_TOKEN_VALID to + it?.tokenDetails?.naviSdkToken.isNotNullAndNotEmpty().toString() + ) + ) + if (it?.isTurboCheckoutFlow.orFalse()) { + if (it?.tokenDetails?.naviSdkToken.isNotNullAndNotEmpty()) { + val paymentSdkInitParams = + PaymentSdkInitParams( + token = it?.tokenDetails?.naviSdkToken!!, + requestId = it.tokenDetails.transactionId, + mintToken = false, + paymentPreFetchMethodRequest = + PaymentPrefetchMethodRequest( + callSdkExitOnBack = false, + previousScreenName = PaymentSource.AMC.name + ), + screenType = Constants.MINI_PAYMENT_SCREEN, + paymentSource = PaymentSource.AMC.name + ) + paymentSharedVM.paymentFlowType = Constant.FLOW_TYPE_LUMPSUM_PURCHASE + val bundle = + Bundle().apply { + putAll(arguments) + putString( + Constant.TRANSACTION_ID, + viewModel.paymentInitiateData.value?.tokenDetails?.transactionId + ) + putParcelable( + Constant.REQUEST_CONFIG, + viewModel.paymentInitiateData.value?.requestConfig + ) + putString( + Constant.ORDER_ID, + viewModel.paymentInitiateData.value?.tokenDetails?.naviOrderId + ) + } + paymentSharedVM.paymentArgsDataMap = bundleToMap(bundle) + naviCheckoutViewModel.initiatePayment(paymentSdkInitParams) + } + } else if (it?.syncFlow.orFalse()) { + paymentSharedVM.isSyncFlow = true + if (it?.tokenDetails?.naviSdkToken.isNotNullAndNotEmpty()) { + paymentSharedVM.syncFlowPaymentInitStartTime = System.currentTimeMillis() + sendEvent(AmcAnalytics.AMC_INIT_PAYMENT) + paymentVM.clear() + paymentVM.init( + it?.tokenDetails?.naviSdkToken!!, + requireActivity(), + (requireActivity() as? PaymentListener) + ) + } + } else { + hideLoader() + it?.let { + TempStorageHelper.checkerResponse = it.data?.checkerData + fragmentInterchangeListener?.navigateToNextScreen( + viewModel.getSelectedFooter()?.nextCta, + arguments ?: Bundle() + ) + } + } + } + + paymentVM.paymentAnalyticsEvent.observe(viewLifecycleOwner) { + val attributes = + hashMapOf().apply { + it?.extraAttributes?.let { attributes -> putAll(attributes) } + } + sendEvent("${AMC.lowercase()}$UNDERSCORE${it?.eventName}", attributes) + } + + paymentSharedVM.postPaymentStatus.observe(viewLifecycleOwner) { data -> + data?.let { + paymentVM.postPaymentStatus(it) + val url = getPaymentSyncFlowStatusCta(CheckerActivity.PAYMENT_CALLBACK_SYNC) + val bundle = + Bundle().apply { + putString( + Constant.TRANSACTION_ID, + viewModel.paymentInitiateData.value?.tokenDetails?.transactionId + ) + putParcelable( + Constant.REQUEST_CONFIG, + viewModel.paymentInitiateData.value?.requestConfig + ) + putString( + Constant.ORDER_ID, + viewModel.paymentInitiateData.value?.tokenDetails?.naviOrderId + ) + putString(PaymentAnalytics.SYNC, PaymentAnalytics.TRUE) + putString(PaymentAnalytics.PROVIDER, it.provider) + } + fragmentInterchangeListener?.navigateToNextScreen(ActionData(url = url), bundle) + } + } + } + + private fun actionListener(action: ActionData) { + sendEvent(eventsData = action.metaData?.clickedData) + if (action.type == DISMISS) return + when (action.url) { + Constant.BACK_PRESS -> { + fragmentInterchangeListener?.navigateToNextScreen(null) + } + NotificationConstants.DISMISS -> { + // Do nothing + } + else -> { + navigate(action) + } + } + } + + private fun setFooter() { + val newFooterData = viewModel.getPaymentFooter() + if (newFooterData != null) { + if (newFooterData is AmountPageFooter) { + if (viewModel.isSipSelected.orFalse()) { + binding.footer.root.isVisible = false + binding.paymentFooter.apply { + root.isVisible = true + accountContainer.background = + getNaviDrawable( + cornerRadius = + resources.getDimension(com.navi.design.R.dimen.dp_4).toInt(), + backgroundColor = + newFooterData.paymentCta + ?.accountBackgroundColor + .parseColorSafe(WHITE) + ) + accountContainer.setBackgroundColor( + newFooterData.paymentCta?.accountBackgroundColor.parseColorSafe(WHITE) + ) + account.setSpannableString(newFooterData.paymentCta?.account) + accountLeftIcon.showWhenDataIsAvailable( + newFooterData.paymentCta?.accountLeftIcon + ) + newFooterData.paymentCta?.let { + rightIcon.showWhenDataIsAvailable(it.rightIcon) + actionText.showWhenDataIsAvailable( + it.actionText, + clickListener = { + actionText.isPressed = false + actionText.invalidate() + handleOnClick(it) + } + ) + if (viewModel.paymentMode.value == null) { + amount.setSpannableString(it.paymentMode) + viewModel.paymentMode.value = it.paymentMode?.text + buyFlowVM.paymentMode.value = it.paymentMode?.text + leftIcon.showWhenDataIsAvailable(it.leftIcon) + } else { + amount.setText(viewModel.getPaymentModeText()) + leftIcon.showWhenDataIsAvailable(viewModel.paymentMode.value) + } + it.actionData?.let { actionData -> handleOnClick(actionData) } + } + btn.text = newFooterData.nextCta?.title + sipNextCtaText = newFooterData.nextCta?.title + btn.setOnClickListener { + if (binding.sipAmount.getUserInputPostValidation() != null) { + handleOnClick(newFooterData.nextCta) + } + } + } + } else { + binding.footer.root.isVisible = false + binding.paymentFooter.apply { + root.isVisible = true + accountContainer.background = + getNaviDrawable( + cornerRadius = + resources.getDimension(com.navi.design.R.dimen.dp_4).toInt(), + backgroundColor = + newFooterData.paymentCta + ?.accountBackgroundColor + .parseColorSafe(WHITE) + ) + accountContainer.setBackgroundColor( + newFooterData.paymentCta?.accountBackgroundColor.parseColorSafe(WHITE) + ) + account.setSpannableString(newFooterData.paymentCta?.account) + accountLeftIcon.showWhenDataIsAvailable( + newFooterData.paymentCta?.accountLeftIcon + ) + newFooterData.paymentCta?.let { + val lumpsumAmount = binding.lumpsumAmount.getUserInput() + amount.text = + CurrencyUtils.getNormalizedAmount( + if (!lumpsumAmount.isNullOrEmpty()) lumpsumAmount.toBigDecimal() + else BigDecimal(0) + ) + leftIcon.showWhenDataIsAvailable(it.leftIcon) + rightIcon.showWhenDataIsAvailable(it.rightIcon) + actionText.showWhenDataIsAvailable(it.actionText) + it.actionData?.let { actionData -> + llAction.setOnClickListener { view -> + sendEvent(actionData.metaData?.clickedData) + if ( + binding.lumpsumAmount.getUserInputPostValidation() != null + ) { + val bundle = + createLumpSumPaymentBundle().apply { + if ( + confinedInvestmentType != + AmcAnalytics.SIP_AND_LUMPSUM + ) { + putString( + Constant.CONFINED_INVESTMENT_TYPE, + confinedInvestmentType + ) + } + } + fragmentInterchangeListener?.navigateToNextScreen( + actionData, + bundle + ) + } + } + } + } + btn.text = newFooterData.nextCta?.title + btn.setOnClickListener { validation() } + } + } + } else if (newFooterData is GenericFooter) { + binding.paymentFooter.root.isVisible = false + binding.footer.root.isVisible = true + binding.footer.btn.apply { + text = newFooterData.nextCta?.title + setOnClickListener { validation() } + } + } + } else { + val footer = viewModel.getSelectedFooter() + footer?.let { + binding.paymentFooter.root.isVisible = false + binding.footer.root.visibility = View.VISIBLE + binding.footer.btn.apply { + text = footer.nextCta?.title + setTextColor(footer.nextCta?.titleColor.parseColorSafe()) + setOnClickListener { validation() } + } + } + } + } + + private fun setRewardCalloutWidgetData( + binding: FundBuyingFragmentV3Binding, + rewardCalloutWidgetData: RewardCalloutWidgetData + ) { + rewardCalloutWidgetBinding = + RewardCalloutWidgetLayoutBinding.inflate(LayoutInflater.from(requireContext())) + + val widgetData = + RewardCalloutWidget( + widgetId = null, + widgetData = rewardCalloutWidgetData, + isDependentWidget = null, + widgetError = null, + dependencyWidgetId = null, + isDependencyWidgetShowing = null + ) + + binding.rewardCallout.isVisible = true + + (rewardCalloutWidgetBinding.root as RewardCalloutWidgetLayout).update( + binding = rewardCalloutWidgetBinding, + widgetData = widgetData, + widgetCallback = this, + position = 0 + ) + + val layoutParams = + ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + + rewardCalloutWidgetBinding.root.layoutParams = layoutParams + binding.rewardCallout.addView(rewardCalloutWidgetBinding.root) + } + + private fun setTimerWithImageWidgetData( + timerWithImageWidgetData: com.navi.naviwidgets.models.TimerWithImageWidgetData + ) { + timerWithImageWidgetLayoutBinding = + TimerWithImageWidgetLayoutBinding.inflate(LayoutInflater.from(requireContext())) + + binding.cutoffBanner.isVisible = true + (timerWithImageWidgetLayoutBinding.root as TimerWithImageWidgetLayout).update( + binding = timerWithImageWidgetLayoutBinding, + widgetData = timerWithImageWidgetData, + widgetCallback = this, + position = 0, + genericTimer = genericTimer + ) + + val layoutParams = + ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + + timerWithImageWidgetLayoutBinding.root.layoutParams = layoutParams + binding.cutoffBanner.addView(timerWithImageWidgetLayoutBinding.root) + } + + override fun onDataChanged( + widgetId: String?, + ) { + if (widgetId == TIMER_WITH_IMAGE_WIDGET) { + bottomSheetSharedVM.setVisibility(false) + fetchScreenData(genericTimer.isInitialised()) + } + } + + private fun navigate(action: ActionData) { + fragmentInterchangeListener?.navigateToNextScreen(action) + } + + override fun onClick(naviClickAction: NaviClickAction, widgetId: String?) { + when (naviClickAction) { + is TextSearchClickAction -> { + val data = + naviClickAction.labeledTextInputSearchWidgetModel + ?.widgetData + ?.textSearchItemData + ?.bottomSheetData + openBottomSheet(data) + } + is NaviWidgetClickWithActionData -> { + if (naviClickAction.actionData?.url == ADD_AMOUNT) { + handleAddAmountWidgetClicked(naviClickAction) + } else if (naviClickAction.actionData?.url == SHOW_BOTTOMSHEET) { + val bundle = + Bundle().apply { + putString(DATA, naviClickAction.actionData?.parameters?.get(0)?.value) + } + AmcCommonBottomSheet.newInstance(bundle, null).apply { + safelyShowBottomSheet(this, AmcCommonBottomSheet.TAG) + } + } else { + when (viewModel.sipType) { + WEEKLY -> { + val data = firstInstallmentWeeklyBottomSheetData + openBottomSheet(data) + } + MONTHLY -> { + if (viewModel.sipAmount != null && viewModel.maxMandateLimit != null) { + val userAmount = viewModel.sipAmount!!.toDouble() + val mandateLimit = viewModel.maxMandateLimit!!.toDouble() + val firstInstallmentMap = + installmentDateMapMaker( + MONTHLY, + FIRST_INSTALLMENT_DATE, + (userAmount <= mandateLimit), + viewModel.paymentMode.value + ) + val nextInstallmentMap = + installmentDateMapMaker( + MONTHLY, + NEXT_INSTALLMENT_DATE, + (userAmount <= mandateLimit), + viewModel.paymentMode.value + ) + val bundle = + Bundle().apply { + putString( + DATA, + naviClickAction.actionData?.parameters?.get(2)?.value + ) + firstInstallmentMap?.let { + putParcelableArrayList( + FIRST_INSTALLMENT_DATE, + it as ArrayList + ) + } + nextInstallmentMap?.let { + putParcelableArrayList( + NEXT_INSTALLMENT_DATE, + it as ArrayList + ) + } + } + val bottomSheet = + AmcDynamicBottomSheet.newInstance( + bundle, + subtitleListener = null, + action = { action -> + val selectedDate = + action.parameters + ?.firstOrNull { it.key == CALENDAR_DATE } + ?.value + selectedDate?.toInt()?.let { intDate -> + monthlySelectedDateId = intDate + binding.date.apply { + setText( + selectedDate + + getDateSuffix(intDate) + + SPACE + + resources.getString( + R.string.of_every_month + ) + ) + } + viewModel.sipDateId = selectedDate + viewModel.monthlyDefaultSipDateText = + selectedDate + + getDateSuffix(intDate) + + SPACE + + resources.getString(R.string.of_every_month) + } + }, + prevSelectedDate = monthlySelectedDateId + ) + safelyShowBottomSheet(bottomSheet, AmcDynamicBottomSheet.TAG) + } + } + else -> { + handleOnClick(naviClickAction.actionData) + } + } + } + } + } + } + + private fun installmentDateMapMaker( + sipType: String, + installmentType: String, + noNewMandateRequired: Boolean, + paymentMode: String? + ): List? { + val installmentDates = + when (paymentMode) { + NET_BANKING -> nbInstallmentDates + UPI -> upiInstallmentDates + else -> null + } + val installmentDatesMap = + when (sipType) { + WEEKLY -> installmentDates?.weekly + MONTHLY -> installmentDates?.monthly + else -> null + } + val installmentDatesList = mutableListOf() + when (installmentType) { + FIRST_INSTALLMENT_DATE -> { + installmentDatesMap?.firstInstallmentDate?.let { + for (i in it.indices) { + installmentDatesList.add( + LineItem( + key = it[i].id, + value = + if (noNewMandateRequired) it[i].amountLessThanMandate + else it[i].amountGreaterThanMandate + ) + ) + } + } + } + NEXT_INSTALLMENT_DATE -> { + installmentDatesMap?.nextInstallmentDate?.let { + for (i in it.indices) { + installmentDatesList.add( + LineItem( + key = it[i].id, + value = + if (noNewMandateRequired) it[i].amountLessThanMandate + else it[i].amountGreaterThanMandate + ) + ) + } + } + } + } + return installmentDatesList + } + + private fun handleOnClick(action: ActionData?) { + sendEvent( + action?.metaData?.clickedData, + ) + if (action?.type == DISMISS) return + val url = action?.url + if (url == SHOW_BOTTOMSHEET) { + var data = action.parameters?.getOrNull(0)?.value + val key = action.parameters?.getOrNull(0)?.key + if (key == "AMC_PAYMENT_BOTTOMSHEET") { + if ( + viewModel.sipAmount.toDoubleWithSafe() > + viewModel.paymentModeLimit.toDoubleWithSafe() + ) { + data = action.parameters?.getOrNull(1)?.value + } + } + val bundle = + Bundle().apply { + putString(DATA, data) + putString(PAYMENT_MODE, buyFlowVM.paymentMode.value) + } + key?.let { key -> + getBottomSheet( + key, + bundle, + genericListener = { action -> + if (key == "AMC_PAYMENT_BOTTOMSHEET") { + viewModel.setPaymentMode(action) + } else { + handleOnClick(action) + } + }, + ) + ?.let { safelyShowBottomSheet(it, key) } + } + } else if (url == Constant.BACK_PRESS) { + fragmentInterchangeListener?.navigateToNextScreen(null) + } else if (url == GET_SIP_SUMMARY_BOTTOMSHEET) { + val sipOrderSummary = + SipOrderSummaryData( + isin = arguments?.getString(AmcAnalytics.ISIN).orEmpty(), + amount = viewModel.sipAmount, + frequency = viewModel.sipType, + sipDateId = viewModel.sipDateId.orElse("1"), + paymentMode = buyFlowVM.paymentMode.value, + screenName = screenName + ) + updateLoadingState(true) + buyFlowVM.getSipOrderSummaryData( + sipOrderSummaryData = sipOrderSummary, + screenName = screenName + ) + } else { + fragmentInterchangeListener?.navigateToNextScreen(actionData = action) + } + } + + private fun setRecommendedPills( + chipData: List, + sipPills: Boolean, + isSipTypeUpdation: Boolean? = false + ) { + if (sipPills) { + if (isSipTypeUpdation == true) { + binding.sipRecommended.updateChipData(chipData) + viewModel.sipAmount?.let { binding.sipRecommended.setSelectedAmount(it) } + } else { + binding.sipRecommended.setProperties(chipData) { sipRecommended, action -> + val updatedAmount = + viewModel.onChipSelected( + binding.sipAmount.getUserInput(), + sipRecommended, + action + ) + binding.sipAmount.updateText(updatedAmount) + val eventName = + if (action?.url.equals(ADD_AMOUNT)) + AmcAnalytics.AMC_BTN_INVEST_SETUP_AMOUNT_INCREMENT_PILLS + else AmcAnalytics.AMC_BTN_INVEST_SETUP_AMOUNT_RECO + sendEvent( + eventName, + hashMapOf( + AmcAnalytics.TRANS_TYPE to Constant.SIP, + AMOUNT to binding.sipAmount.getUserInput().orEmpty(), + AmcAnalytics.FUND_ID to + arguments?.getString(AmcAnalytics.ISIN).orEmpty() + ) + ) + } + } + viewModel.lastSipInteraction = AmcAnalytics.PILL + viewModel.toShowSipRecommended = chipData.isNotEmpty() + } else { + binding.lumpsumRecommended.setProperties(chipData) { lsRecommended, action -> + val updatedAmount = + viewModel.onChipSelected( + binding.lumpsumAmount.getUserInput(), + lsRecommended, + action + ) + binding.lumpsumAmount.updateText(updatedAmount) + val eventName = + if (action?.url.equals(ADD_AMOUNT)) + AmcAnalytics.AMC_BTN_INVEST_SETUP_AMOUNT_INCREMENT_PILLS + else AmcAnalytics.AMC_BTN_INVEST_SETUP_AMOUNT_RECO + sendEvent( + eventName, + hashMapOf( + AmcAnalytics.TRANS_TYPE to Constant.LUMPSUM, + AMOUNT to binding.lumpsumAmount.getUserInput().orEmpty(), + AmcAnalytics.FUND_ID to arguments?.getString(AmcAnalytics.ISIN).orEmpty() + ) + ) + viewModel.lastLumpsumInteraction = AmcAnalytics.PILL + } + viewModel.toShowLumpsumRecommended = true + } + } + + private fun openBottomSheet(data: SingleSelectionBottomSheetData?) { + val bottomSheet = SingleSelectionBottomSheet.getInstance(data) + safelyShowBottomSheet(bottomSheet, SingleSelectionBottomSheet.SINGLE_SELECTION_BOTTOM_SHEET) + bottomSheet.selectedItem.observeNonNull(this) { item -> + if (item.id in listOf(DAILY, MONTHLY, WEEKLY)) { + binding.sipAmount.visibility = View.VISIBLE + viewModel.sipType = item.id + viewModel.setSelectedSipType(viewModel.sipType) + binding.sipRecommended.isVisible = viewModel.toShowSipRecommended + if (item.id == DAILY) { + viewModel.toHideDate = true + binding.date.visibility = View.GONE + } else { + viewModel.toHideDate = false + binding.date.visibility = View.VISIBLE + binding.container.post { binding.container.fullScroll(View.FOCUS_DOWN) } + } + binding.sipFrequency.setText(item) + (viewModel.fundBuyScreenData.value + ?.content + ?.fundInvestmentType + ?.sipData + ?.getOrNull(0) as? TextSearchWidgetModel) + ?.widgetData + ?.textSearchItemData + ?.savedText = item + } else { + viewModel.sipDateId = item.id + viewModel.selectedDate = item.title?.text + binding.date.apply { + item.title?.text?.let { + viewModel.weeklyDefaultSipDateText = "Every $it" + setText("Every $it") + } + } + } + } + } + + private fun getCreateSipEventName(): String? { + + if (viewModel.isSipSelected.orFalse().not()) return null + + return if (confinedInvestmentType == AmcAnalytics.SIP) { + AmcAnalytics.AMC_BTN_SIMPLIFIED_SIP_CREATESIP + } else { + AmcAnalytics.AMC_BTN_INVEST_SETUP_CREATE_SIP + } + } + + private fun initiateSip(action: ActionData?) { + if (action?.url == DISMISS) { + // do nothing + return + } + buyFlowVM.paymentMode.value = + action?.parameters?.firstOrNull { it.key == PAYMENT_MODE }?.value + viewModel.paymentMode.value = buyFlowVM.paymentMode.value + viewModel.selectedDate = action?.parameters?.firstOrNull { it.key == SIP_DATE }?.value + val nextCtaUrl = action?.url.orEmpty() + val isin = arguments?.getString(AmcAnalytics.ISIN) + val amount = binding.sipAmount.getUserInputPostValidation() + val frequency = viewModel.sipType + val sipDate = viewModel.selectedDate + val paymentMode = buyFlowVM.paymentMode.value + val bankDetailsRefId = + action?.parameters?.firstOrNull { it.key == BANK_DETAILS_REF_ID }?.value + val sipNextInstallmentDate = + action?.parameters?.firstOrNull { it.key == SIP_NEXT_INSTALLMENT_DATE }?.value + + if (nextCtaUrl.endsWith(SubPageStatusType.OTP, true) && amount != null) { + val bundle = + Bundle().apply { + putString(AmcAnalytics.ISIN, isin) + putString(AMOUNT, amount) + putString(FREQUENCY, frequency) + putString(SIP_DATE, sipDate) + putString(DELETED_SIP_REFERENCE_ID, deletedSipReferenceId) + putString(PAYMENT_MODE, paymentMode) + putString(BANK_DETAILS_REF_ID, bankDetailsRefId) + putString(SIP_NEXT_INSTALLMENT_DATE, sipNextInstallmentDate) + if (confinedInvestmentType != AmcAnalytics.SIP_AND_LUMPSUM) { + putString(Constant.CONFINED_INVESTMENT_TYPE, confinedInvestmentType) + } + } + fragmentInterchangeListener?.navigateToNextScreen(action, bundle) + } else { + val sipDetails = + SipDetailsData( + scheme = isin, + amount = amount, + frequency = frequency, + sipDate = sipDate + ) + showLoader() + viewModel.postSipDetails(sipDetails) + } + } + + private fun validation() { + if (viewModel.isSipSelected.orFalse()) { + if (viewModel.toHideDate) { + val frequency = binding.sipFrequency.getUserInputPostValidation() + if (frequency.isNullOrEmpty()) return + val amount = binding.sipAmount.getUserInputPostValidation() + if (amount != null) { + sendEvent( + getCreateSipEventName(), + hashMapOf( + Pair(AmcAnalytics.IS_VALID, AmcAnalytics.TRUE), + Pair( + AmcAnalytics.FUND_ID, + arguments?.getString(AmcAnalytics.ISIN).orEmpty() + ), + Pair(AmcAnalytics.SIP_FREQUENCY, frequency), + Pair(AMOUNT, amount), + Pair(Constant.AMOUNT_SOURCE, viewModel.lastSipInteraction), + Pair( + AmcAnalytics.ACTIVE_MANDATE, + viewModel.isAutoPayThere().toString() + ), + Pair( + AmcAnalytics.REUSE_MANDATE, + (if (viewModel.isAutoPayThere()) + binding.autopayCheckbox.checkbox.isChecked + else false) + .toString() + ) + ), + isNeededForFirebase = true + ) + if (viewModel.isManualVsAutopayExperiment()) { + sipWeeklyFortnightlyAction(null) + } else { + var bottomSheetData: String? + viewModel.fundBuyScreenData.value?.content?.isDynamicMandateEnabled.let { + isDynamicMandateEnabled -> + if ( + isDynamicMandateEnabled.isNotNull() && + isDynamicMandateEnabled == true + ) { + sipWeeklyFortnightlyAction(null) + return + } else { + bottomSheetData = + viewModel.getSipBottomSheetData( + binding.sipAmount.widgetBinding.plainTextInput.text + .toString(), + frequency, + binding.autopayCheckbox.checkbox.isChecked + ) + } + } + + val bundle = Bundle().apply { putString(DATA, bottomSheetData) } + getBottomSheet( + SubPageStatusType.AMC_COMMON_BOTTOMSHEET, + bundle = bundle, + genericListener = ::sipWeeklyFortnightlyAction + ) + ?.let { + safelyShowBottomSheet( + it, + SubPageStatusType.HORIZONTAL_BUTTON_BOTTOMSHEET + ) + } + } + } else { + sendEvent( + getCreateSipEventName(), + hashMapOf( + Pair(AmcAnalytics.IS_VALID, AmcAnalytics.FALSE), + Pair( + AmcAnalytics.FUND_ID, + arguments?.getString(AmcAnalytics.ISIN).orEmpty() + ), + Pair(AmcAnalytics.SIP_FREQUENCY, frequency.orEmpty()), + Pair(AMOUNT, amount.orEmpty()), + Pair(Constant.AMOUNT_SOURCE, viewModel.lastSipInteraction), + Pair( + AmcAnalytics.ACTIVE_MANDATE, + viewModel.isAutoPayThere().toString() + ), + Pair( + AmcAnalytics.REUSE_MANDATE, + (if (viewModel.isAutoPayThere()) + binding.autopayCheckbox.checkbox.isChecked + else false) + .toString() + ) + ), + isNeededForFirebase = true + ) + } + } else { + val frequency = binding.sipFrequency.getUserInputPostValidation() + if (frequency.isNullOrEmpty()) return + val amount = binding.sipAmount.getUserInputPostValidation() + val date = binding.date.getUserInputPostValidation() + if (amount != null && date != null) { + sendEvent( + getCreateSipEventName(), + hashMapOf( + Pair(AmcAnalytics.IS_VALID, AmcAnalytics.TRUE), + Pair( + AmcAnalytics.FUND_ID, + arguments?.getString(AmcAnalytics.ISIN).orEmpty() + ), + Pair(AmcAnalytics.SIP_FREQUENCY, frequency), + Pair(AmcAnalytics.SIP_DATE, date), + Pair(AMOUNT, amount), + Pair(Constant.AMOUNT_SOURCE, viewModel.lastSipInteraction), + Pair( + AmcAnalytics.ACTIVE_MANDATE, + viewModel.isAutoPayThere().toString() + ), + Pair( + AmcAnalytics.REUSE_MANDATE, + (if (viewModel.isAutoPayThere()) + binding.autopayCheckbox.checkbox.isChecked + else false) + .toString() + ) + ), + isNeededForFirebase = true + ) + if (viewModel.isManualVsAutopayExperiment()) { + sipMonthlyAction(null) + } else { + var bottomSheetData: String? + viewModel.fundBuyScreenData.value?.content?.isDynamicMandateEnabled.let { + isDynamicMandateEnabled -> + if ( + isDynamicMandateEnabled.isNotNull() && + isDynamicMandateEnabled == true + ) { + sipMonthlyAction(null) + return + } else { + bottomSheetData = + viewModel.getSipBottomSheetData( + binding.sipAmount.widgetBinding.plainTextInput.text + .toString(), + frequency, + binding.autopayCheckbox.checkbox.isChecked + ) + } + } + + val bundle = Bundle().apply { putString(DATA, bottomSheetData) } + getBottomSheet( + SubPageStatusType.AMC_COMMON_BOTTOMSHEET, + bundle = bundle, + genericListener = ::sipMonthlyAction + ) + ?.let { + safelyShowBottomSheet( + it, + SubPageStatusType.HORIZONTAL_BUTTON_BOTTOMSHEET + ) + } + } + } else { + sendEvent( + getCreateSipEventName(), + hashMapOf( + Pair(AmcAnalytics.IS_VALID, AmcAnalytics.FALSE), + Pair( + AmcAnalytics.FUND_ID, + arguments?.getString(AmcAnalytics.ISIN).orEmpty() + ), + Pair(AmcAnalytics.SIP_FREQUENCY, frequency), + Pair(AmcAnalytics.SIP_DATE, date.orEmpty()), + Pair(AMOUNT, amount.orEmpty()), + Pair(Constant.AMOUNT_SOURCE, viewModel.lastSipInteraction), + Pair( + AmcAnalytics.ACTIVE_MANDATE, + viewModel.isAutoPayThere().toString() + ), + Pair( + AmcAnalytics.REUSE_MANDATE, + (if (viewModel.isAutoPayThere()) + binding.autopayCheckbox.checkbox.isChecked + else false) + .toString() + ) + ), + isNeededForFirebase = true + ) + } + } + } else { + if (binding.lumpsumAmount.getUserInputPostValidation() != null) { + sendEvent( + AmcAnalytics.AMC_BTN_INVEST_SETUP_CREATE_LUMPSUM, + hashMapOf( + Pair(AmcAnalytics.IS_VALID, AmcAnalytics.TRUE), + Pair(AMOUNT, binding.lumpsumAmount.getUserInput().toString()), + Pair( + AmcAnalytics.FUND_ID, + arguments?.getString(AmcAnalytics.ISIN).orEmpty() + ), + Pair(Constant.AMOUNT_SOURCE, viewModel.lastLumpsumInteraction) + ), + isNeededForFirebase = true + ) + + sendEvent( + viewModel.getSelectedFooter()?.nextCta?.metaData?.clickedData, + hashMapOf( + Pair(AmcAnalytics.IS_VALID, AmcAnalytics.TRUE), + Pair( + AmcAnalytics.FUND_ID, + arguments?.getString(AmcAnalytics.ISIN).orEmpty() + ) + ) + ) + + val bottomSheetData = viewModel.getLumpsumBottomSheetData() + bottomSheetData?.let { + val bsheetDataBundle = Bundle().apply { putString(DATA, it) } + getBottomSheet( + SubPageStatusType.AMC_COMMON_BOTTOMSHEET, + bundle = bsheetDataBundle, + genericListener = ::processLumpsumPayment + ) + ?.let { + safelyShowBottomSheet(it, SubPageStatusType.AMC_COMMON_BOTTOMSHEET) + } + } ?: run { processLumpsumPayment(ActionData()) } + } else { + + if (confinedInvestmentType == AmcAnalytics.SIP_AND_LUMPSUM) { + sendEvent( + AmcAnalytics.AMC_BTN_INVEST_SETUP_CREATE_LUMPSUM, + hashMapOf( + Pair(AmcAnalytics.IS_VALID, AmcAnalytics.FALSE), + Pair( + AmcAnalytics.FUND_ID, + arguments?.getString(AmcAnalytics.ISIN).orEmpty() + ), + Pair(AMOUNT, binding.lumpsumAmount.getUserInput().toString()), + Pair(Constant.AMOUNT_SOURCE, viewModel.lastLumpsumInteraction) + ), + isNeededForFirebase = true + ) + } else if (confinedInvestmentType == AmcAnalytics.LUMPSUMP) { + sendEvent( + viewModel.getSelectedFooter()?.nextCta?.metaData?.clickedData, + hashMapOf( + Pair(AmcAnalytics.IS_VALID, AmcAnalytics.FALSE), + Pair( + AmcAnalytics.FUND_ID, + arguments?.getString(AmcAnalytics.ISIN).orEmpty() + ) + ) + ) + } + } + } + } + + private fun createLumpSumPaymentBundle(): Bundle { + return Bundle().apply { + putParcelable( + Constant.FUND_HEADER, + viewModel.fundBuyScreenData.value?.content?.fundHeaderData + ) + putString(AMOUNT, binding.lumpsumAmount.getUserInput()) + putString(AmcAnalytics.ISIN, arguments?.getString(AmcAnalytics.ISIN)) + putString(Constant.TYPE, Constant.LUMPSUM) + } + } + + private fun processLumpsumPayment(action: ActionData) { + val ctaUrl = viewModel.getSelectedFooter()?.nextCta?.url.orEmpty() + val bundle = createLumpSumPaymentBundle() + + if (action.url == DISMISS) { + // do nothing + return + } + + if (ctaUrl.endsWith(SubPageStatusType.OTP, true)) { + fragmentInterchangeListener?.navigateToNextScreen( + viewModel.getSelectedFooter()?.nextCta, + bundle + ) + } else if (ctaUrl.contains(Constant.URL_AMC_CHECKER_INITIATE_PAYMENT, true)) { + showLoader() + viewModel.initiatePayment( + PaymentPostData( + amount = binding.lumpsumAmount.getUserInput()?.toDoubleOrNull(), + scheme = arguments?.getString(AmcAnalytics.ISIN), + paymentMode = Constant.UPI + ), + screenName = screenName + ) + } else { + fragmentInterchangeListener?.navigateToNextScreen( + viewModel.getSelectedFooter()?.nextCta, + bundle + ) + } + } + + private fun createSipRecurringAction(action: ActionData?, includeSipDate: Boolean) { + if (action?.url == DISMISS) { + // do nothing + return + } + + val ctaUrl = viewModel.getSelectedFooter()?.nextCta?.url.orEmpty() + val isin = arguments?.getString(AmcAnalytics.ISIN) + val amount = binding.sipAmount.getUserInputPostValidation() + val frequency = viewModel.sipType + val sipDate = viewModel.selectedDate + val autoPayChecked = + if (viewModel.isAutoPayThere()) binding.autopayCheckbox.checkbox.isChecked.toString() + else null + + if ( + ctaUrl.endsWith(SubPageStatusType.OTP, true) || + ctaUrl.endsWith(SubPageStatusType.SIP_TYPE, true) + ) { + val bundle = + Bundle().apply { + putString(AmcAnalytics.ISIN, isin) + putString(AMOUNT, amount) + putString(FREQUENCY, frequency) + putString(AUTOPAY_CHECKED, autoPayChecked) + putString(DELETED_SIP_REFERENCE_ID, deletedSipReferenceId) + + if (includeSipDate) { + putString(SIP_DATE, sipDate) + } + + if (confinedInvestmentType != AmcAnalytics.SIP_AND_LUMPSUM) { + putString(Constant.CONFINED_INVESTMENT_TYPE, confinedInvestmentType) + } + } + + fragmentInterchangeListener?.navigateToNextScreen( + viewModel.getSelectedFooter()?.nextCta, + bundle + ) + } else { + + val sipDetails = + SipDetailsData( + scheme = isin, + amount = amount, + frequency = frequency, + sipDate = if (includeSipDate) sipDate else null, + autoPayChecked = autoPayChecked?.toBoolean(), + deletedSipReferenceId = deletedSipReferenceId + ) + + showLoader() + viewModel.postSipDetails(sipDetails) + } + } + + private fun sipWeeklyFortnightlyAction(action: ActionData?) = + createSipRecurringAction(action, false) + + private fun sipMonthlyAction(action: ActionData?) = createSipRecurringAction(action, true) + + override fun getStartScreenEventAttributes(): Map { + return mutableMapOf(AmcAnalytics.FUND_ID to isin.orEmpty()).apply { + super.getStartScreenEventAttributes()?.let { this.putAll(it) } + } + } + + private fun handleAddAmountWidgetClicked(naviClickAction: NaviWidgetClickWithActionData) { + val isSipSelected = viewModel.isSipSelected.orFalse() + val currWidget = + (if (isSipSelected) + viewModel.fundBuyScreenData.value + ?.content + ?.fundInvestmentType + ?.sipData + ?.getOrNull(1) + else + viewModel.fundBuyScreenData.value + ?.content + ?.fundInvestmentType + ?.lumpsumData + ?.getOrNull(0)) + as? LabeledTextInputFixedHintWidgetModel + val currentAmount = + if (isSipSelected) binding.sipAmount.getUserInput() + else binding.lumpsumAmount.getUserInput() + val updatedAmount = + viewModel.onChipSelected( + currentAmount, + currWidget?.widgetData?.endButtonData?.key, + naviClickAction.actionData + ) + if (isSipSelected) { + binding.sipAmount.updateText(updatedAmount) + } else { + binding.lumpsumAmount.updateText(updatedAmount) + } + sendEvent( + AmcAnalytics.AMC_BTN_INVEST_SETUP_AMOUNT_INCREMENT_FIELD, + hashMapOf( + AmcAnalytics.TRANS_TYPE to Constant.LUMPSUM, + AMOUNT to binding.lumpsumAmount.getUserInput().orEmpty(), + AmcAnalytics.FUND_ID to arguments?.getString(AmcAnalytics.ISIN).orEmpty() + ) + ) + } + + private fun autoFillSipData(content: FundBuyData) { + + val sipFrequency = arguments?.getString(AmcAnalytics.SIP_FREQUENCY) + val sipAmount = arguments?.getString(AMOUNT) + val sipDate = arguments?.getString(AmcAnalytics.SIP_DATE) + + sipFrequency?.let { frequency -> + viewModel.sipType = frequency + viewModel.setSelectedSipType(viewModel.sipType) + binding.sipAmount.visibility = View.VISIBLE + binding.sipRecommended.isVisible = viewModel.toShowSipRecommended + val bottomSheetData = + (content.fundInvestmentType?.sipData?.getOrNull(0) as? TextSearchWidgetModel) + ?.widgetData + ?.textSearchItemData + ?.bottomSheetData + if (frequency == MONTHLY || frequency == WEEKLY) { + viewModel.toHideDate = false + binding.date.visibility = View.VISIBLE + } else { + viewModel.toHideDate = true + binding.date.visibility = View.GONE + } + val lineItemData = bottomSheetData?.items?.find { it.id == frequency } + lineItemData?.let { + binding.sipFrequency.setText(lineItemData) + (viewModel.fundBuyScreenData.value + ?.content + ?.fundInvestmentType + ?.sipData + ?.getOrNull(0) as? TextSearchWidgetModel) + ?.widgetData + ?.textSearchItemData + ?.savedText = lineItemData + } + } + + sipAmount?.let { + binding.sipAmount.updateText(sipAmount) + viewModel.sipAmount = sipAmount + viewModel.checkPaymentModeNetBanking() + } + } + + private fun updateLoadingState(isLoading: Boolean) { + updateButtonLoaderState(isLoading) + if (isLoading) { + (activity as? BaseActivity)?.blockInteractability() + binding.paymentFooter.btn.background = + getNaviDrawable( + cornerRadius = resources.getDimension(com.navi.design.R.dimen.dp_4).toInt(), + backgroundColor = + ContextCompat.getColor( + this.requireContext(), + R.color.button_loader_loading_color + ) + ) + } else { + (activity as? BaseActivity)?.unblockInteractability() + binding.paymentFooter.btn.background = + getNaviDrawable( + cornerRadius = resources.getDimension(com.navi.design.R.dimen.dp_4).toInt(), + backgroundColor = + ContextCompat.getColor( + this.requireContext(), + com.navi.design.R.color.color_1F002A + ) + ) + } + } + + private fun updateButtonLoaderState(state: Boolean) { + sipNextCtaText?.let { + setButtonLoaderState( + binding.paymentFooter.btn, + binding.paymentFooter.buttonLoader, + state, + it + ) + } + } + + companion object { + fun newInstance(bundle: Bundle): FundBuyingFragmentV3 { + return FundBuyingFragmentV3().apply { arguments = bundle } + } + } +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/AutoPaySetupRequestData.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/AutoPaySetupRequestData.kt index c98816d52d..63820b2211 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/AutoPaySetupRequestData.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/AutoPaySetupRequestData.kt @@ -12,5 +12,12 @@ import com.google.gson.annotations.SerializedName data class AutoPaySetupRequestData( @SerializedName("bankAccountRefId") val bankAccountRefId: String? = null, @SerializedName("mandateType") val mandateType: String? = null, - @SerializedName("mandateSipAmount") val amount: String? = null + @SerializedName("mandateSipAmount") val amount: String? = null, + @SerializedName("sipReferenceId") val sipReferenceId: String? = null, +) + +data class MandateWithSipRequestData( + @SerializedName("mandateCreationRequest") + val mandateTokenRequest: AutoPaySetupRequestData? = null, + @SerializedName("sipCreationRequest") val sipCreationRequest: SipDetailsData? = null ) diff --git a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/FundBuyScreenData.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/FundBuyScreenData.kt index ee1ba851a5..701f0a3107 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/FundBuyScreenData.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/FundBuyScreenData.kt @@ -19,6 +19,7 @@ import com.navi.design.textview.model.TextWithStyle import com.navi.design.textview.model.TextWithStyleVariation import com.navi.naviwidgets.models.NaviWidget import com.navi.naviwidgets.models.RewardCalloutWidgetData +import com.navi.naviwidgets.models.SingleSelectionBottomSheetData import com.navi.naviwidgets.models.TimerWithImageWidgetData import com.navi.naviwidgets.models.response.CardType import kotlinx.parcelize.Parcelize @@ -43,6 +44,7 @@ data class FundBuyData( @SerializedName("footerVariations") val footerVariations: Map? = null, @SerializedName("newFooterVariations") val newFooterVariations: Map? = null, @SerializedName("amountPageFooter") val amountPageFooter: CardType? = null, + @SerializedName("sipPageFooter") val sipPageFooter: CardType? = null, // to be removed @SerializedName("isManualVsAutopay") val isManualVsAutopay: Boolean? = null, @SerializedName("sipAutoPayBottomSheetData") val sipAutoPayBottomSheetData: AmcCommonBottomSheetData? = null, @@ -50,7 +52,8 @@ data class FundBuyData( @SerializedName("isDynamicMandateEnabled") val isDynamicMandateEnabled: Boolean? = null, @SerializedName("rewardCalloutWidgetData") val rewardCalloutWidgetData: RewardCalloutWidgetData? = null, - @SerializedName("cutOffTimeData") val cutOffData: TimerWithImageWidgetData? = null + @SerializedName("cutOffTimeData") val cutOffData: TimerWithImageWidgetData? = null, + @SerializedName("paymentModeLimit") val paymentModeLimit: String? = null ) open class GenericFooter( @@ -65,6 +68,7 @@ data class AmountPageFooter( data class PaymentCtaData( @SerializedName("account") val account: TextWithStyle? = null, + @SerializedName("paymentMode") val paymentMode: TextWithStyle? = null, @SerializedName("leftIcon") val leftIcon: String? = null, @SerializedName("rightIcon") val rightIcon: String? = null, @SerializedName("actionText") val actionText: TextWithStyle? = null, @@ -89,6 +93,8 @@ data class FundInvestmentType( @SerializedName("SIP") var sipData: List? = null, @SerializedName("LUMPSUM") var lumpsumData: List? = null, @SerializedName("sipRecommendedAmount") val sipRecommendedAmount: List? = null, + @SerializedName("sipRecommendedAmounts") + val sipRecommendedAmounts: List? = null, @SerializedName("lumpsumRecommendedAmount") val lumpsumRecommendedAmount: List? = null, @SerializedName("lumpsumPaymentMode") val lumpsumPaymentMode: PaymentMode? = null, @@ -96,7 +102,11 @@ data class FundInvestmentType( @SerializedName("sipStartDateOffset") val sipStartDateOffset: SipStartDateOffset? = null, @SerializedName("noteBanner") val noteBanner: NoteBannerData? = null, @SerializedName("simulationData") val simulationData: AmountSimulationData? = null, - @SerializedName("sipSimulationData") val sipSimulationData: AmountSimulationData? = null + @SerializedName("sipSimulationData") val sipSimulationData: AmountSimulationData? = null, + @SerializedName("weeklySipFirstInstallmentDayBottomSheet") + val weeklySipFirstInstallmentDayBottomSheet: SingleSelectionBottomSheetData? = null, + @SerializedName("installmentDates") + val installmentDates: PaymentModeInstallmentDateTypes? = null, ) data class AmountSimulationData( @@ -121,6 +131,11 @@ data class AutoPayData( @SerializedName("isChecked") val isChecked: Boolean? = null ) +data class LabeledAmountChipData( + @SerializedName("id") val id: String? = null, + @SerializedName("recommendedAmount") val recommendedAmount: List? = null +) + data class AmountChipData( @SerializedName("title") val title: TextWithStyleVariation? = null, @SerializedName("label") val label: LabelDataVariation? = null, @@ -140,3 +155,26 @@ data class NoteBannerData( @SerializedName("bannerData") val bannerData: Header? = null, @SerializedName("actionData") val actionData: ActionData? = null ) + +data class PaymentModeInstallmentDateTypes( + @SerializedName("UPI") val upi: InstallmentDateTypes? = null, + @SerializedName("NET_BANKING") val netBanking: InstallmentDateTypes? = null +) + +data class InstallmentDateTypes( + @SerializedName("DAILY") val daily: InstallmentDates? = null, + @SerializedName("WEEKLY") val weekly: InstallmentDates? = null, + @SerializedName("MONTHLY") val monthly: InstallmentDates? = null +) + +data class InstallmentDates( + @SerializedName("firstInstallmentDate") + val firstInstallmentDate: List? = null, + @SerializedName("nextInstallmentDate") val nextInstallmentDate: List? = null +) + +data class InstallmentDateMap( + @SerializedName("id") val id: String? = null, + @SerializedName("amountLessThanMandate") val amountLessThanMandate: String? = null, + @SerializedName("amountGreaterThanMandate") val amountGreaterThanMandate: String? = null +) diff --git a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/SipDetailsData.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/SipDetailsData.kt index 54bd668b17..8f91ff6156 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/SipDetailsData.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/SipDetailsData.kt @@ -13,6 +13,7 @@ data class SipDetailsData( @SerializedName("scheme") val scheme: String? = null, @SerializedName("amount") val amount: String? = null, @SerializedName("sip_date") val sipDate: String? = null, + @SerializedName("sipNextInstallmentDate") val sipNextInstallmentDate: String? = null, @SerializedName("frequency") val frequency: String? = null, @SerializedName("customer_id") val customer_id: String? = null, @SerializedName("autoPayChecked") val autoPayChecked: Boolean? = null, diff --git a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/repository/FundBuyRepository.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/repository/FundBuyRepository.kt index dad982f041..1cc90857ee 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/repository/FundBuyRepository.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/repository/FundBuyRepository.kt @@ -9,12 +9,15 @@ package com.navi.amc.fundbuy.repository import com.navi.amc.common.model.AdditionalDataAsyncResponse import com.navi.amc.common.model.NextCtaResponse +import com.navi.amc.common.model.SipOrderSummaryData +import com.navi.amc.fundbuy.models.AutoPaySetupRequestData import com.navi.amc.fundbuy.models.FundBuyScreenData import com.navi.amc.fundbuy.models.PaymentPostData import com.navi.amc.fundbuy.models.SipDetailsData import com.navi.amc.network.retrofit.RetrofitService import com.navi.amc.utils.AmcAnalytics import com.navi.common.checkmate.model.MetricInfo +import com.navi.common.model.GenericBottomSheetData import com.navi.common.network.models.RepoResult import com.navi.common.network.retrofit.ResponseCallback import javax.inject.Inject @@ -40,9 +43,36 @@ class FundBuyRepository @Inject constructor(private val retrofitService: Retrofi return apiResponseCallback(response = response, metricInfo = metricInfo) } + suspend fun getFundBuyDetailV2( + isin: String, + investmentType: String? = null, + orderAmount: String? = null, + defaultChecked: String? = null, + metricInfo: MetricInfo>? = null + ): RepoResult { + + val response = + when (investmentType) { + AmcAnalytics.SIP -> retrofitService.fetchFundSipBuyDetails(isin) + AmcAnalytics.LUMPSUMP -> retrofitService.fetchFundLumpsumBuyDetails(isin) + else -> retrofitService.fetchFundBuyDetailsV2(isin, orderAmount, defaultChecked) + } + + return apiResponseCallback(response = response, metricInfo = metricInfo) + } + suspend fun postSipDetails(details: SipDetailsData) = apiResponseCallback(retrofitService.postSipDetails(details)) + suspend fun getSipOrderSummary( + sipOrderSummaryData: SipOrderSummaryData, + metricInfo: MetricInfo>? = null + ) = + apiResponseCallback( + response = retrofitService.getSipOrderSummary(sipOrderSummaryData), + metricInfo = metricInfo + ) + suspend fun postPaymentDetails( paymentPostData: PaymentPostData, metricInfo: MetricInfo>> @@ -51,4 +81,13 @@ class FundBuyRepository @Inject constructor(private val retrofitService: Retrofi response = retrofitService.postPaymentDetails(paymentPostData), metricInfo = metricInfo ) + + suspend fun postAutoPaySetupRequestDataV2( + data: AutoPaySetupRequestData, + metricInfo: MetricInfo>>? = null + ) = + apiResponseCallback( + response = retrofitService.postAutoPayDataV2(data), + metricInfo = metricInfo + ) } diff --git a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/viewmodel/FundBuyFlowViewModel.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/viewmodel/FundBuyFlowViewModel.kt index fe0c32c4cd..c3d0c8b5fd 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/viewmodel/FundBuyFlowViewModel.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/viewmodel/FundBuyFlowViewModel.kt @@ -7,9 +7,17 @@ package com.navi.amc.fundbuy.viewmodel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.navi.amc.common.model.AdditionalDataAsyncResponse +import com.navi.amc.common.model.NextCtaResponse +import com.navi.amc.common.model.SipOrderSummaryData import com.navi.amc.common.viewmodel.BaseAmcVM +import com.navi.amc.fundbuy.models.AutoPaySetupRequestData import com.navi.amc.fundbuy.models.ScreenDetails import com.navi.amc.fundbuy.models.Temperature +import com.navi.amc.fundbuy.repository.FundBuyRepository import com.navi.amc.utils.Constant.INVALID_STATE import com.navi.amc.utils.Constant.ORDER_STATUS_TIMESTAMP_FORMAT import com.navi.amc.utils.Constant.SPECIAL_STATE @@ -17,17 +25,22 @@ import com.navi.amc.utils.Constant.V2 import com.navi.amc.utils.Constant.VALID_STATE import com.navi.amc.utils.SubPageStatusType import com.navi.amc.utils.convertDateTimeFromOneFormatToAnother +import com.navi.amc.utils.getAmcMetricInfo import com.navi.amc.utils.getTimeStampFromSystemTime +import com.navi.amc.utils.updateCheckerResponse import com.navi.base.model.ActionData import com.navi.base.model.LineItem +import com.navi.base.utils.isNotNull import com.navi.base.utils.isNull +import com.navi.common.model.GenericBottomSheetData import com.navi.common.utils.Constants.TRUE import com.navi.common.utils.SingleLiveEvent import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @HiltViewModel -class FundBuyFlowViewModel @Inject constructor() : BaseAmcVM() { +class FundBuyFlowViewModel @Inject constructor(private val repository: FundBuyRepository) : + BaseAmcVM() { var fragmentStack: MutableList = mutableListOf() var needToPopTopScreen = false @@ -38,6 +51,17 @@ class FundBuyFlowViewModel @Inject constructor() : BaseAmcVM() { val startPaymentFlow: SingleLiveEvent get() = _startPaymentFlow + private val _sipOrderSummaryResponse = SingleLiveEvent() + val sipOrderSummaryResponse: LiveData + get() = _sipOrderSummaryResponse + + private val _autoPayPaymentInitiateData = + SingleLiveEvent?>() + val autoPayPaymentInitiateData: LiveData?> + get() = _autoPayPaymentInitiateData + + val paymentMode: MutableLiveData = MutableLiveData() + fun setStartPaymentFlow(state: Boolean) { _startPaymentFlow.value = state } @@ -136,4 +160,44 @@ class FundBuyFlowViewModel @Inject constructor() : BaseAmcVM() { formattedTime = getTimeStampFromSystemTime(ORDER_STATUS_TIMESTAMP_FORMAT) return formattedTime } + + fun getSipOrderSummaryData(sipOrderSummaryData: SipOrderSummaryData, screenName: String) { + viewModelScope.safeLaunch { + val response = + repository.getSipOrderSummary( + sipOrderSummaryData = sipOrderSummaryData, + metricInfo = getAmcMetricInfo(screenName) + ) + if ( + response.error == null && + response.errors.isNullOrEmpty() && + response.data.isNotNull() + ) { + _sipOrderSummaryResponse.value = response.data + } else { + _sipOrderSummaryResponse.value = null + setErrorData(response.errors, response.error) + } + } + } + + fun postAutoPaySetupRequestDataV2( + autoPaySetupRequestData: AutoPaySetupRequestData, + screenName: String + ) { + // auto pay setup for only given sip + viewModelScope.safeLaunch { + val response = + repository.postAutoPaySetupRequestDataV2( + autoPaySetupRequestData, + metricInfo = getAmcMetricInfo(screenName) + ) + if (response.error == null && response.errors.isNullOrEmpty()) { + _autoPayPaymentInitiateData.value = updateCheckerResponse(response.data) + } else { + _autoPayPaymentInitiateData.value = null + setErrorData(response.errors, response.error) + } + } + } } diff --git a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/viewmodel/FundBuyV2ViewModel.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/viewmodel/FundBuyV2ViewModel.kt index e7eb6d8ae8..d483de0088 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/viewmodel/FundBuyV2ViewModel.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/viewmodel/FundBuyV2ViewModel.kt @@ -33,7 +33,9 @@ import com.navi.amc.utils.getAmcMetricInfo import com.navi.amc.utils.roundOffAmount import com.navi.amc.utils.updateCheckerResponse import com.navi.base.model.ActionData +import com.navi.base.utils.orFalse import com.navi.base.utils.orZero +import com.navi.base.utils.toDoubleWithSafe import com.navi.common.utils.EMPTY import com.navi.common.utils.formatDateIntoPrefixMonthNamesWithYear import com.navi.common.viewmodel.BaseVM @@ -43,7 +45,6 @@ import com.navi.design.textview.model.TextWithStyle import com.navi.design.utils.parseDateFromOneToAnother import com.navi.naviwidgets.models.NaviWidget import com.navi.naviwidgets.models.response.CardType -import com.navi.uitron.utils.orFalse import dagger.hilt.android.lifecycle.HiltViewModel import java.util.Calendar import java.util.Date @@ -61,6 +62,12 @@ class FundBuyV2ViewModel @Inject constructor(private val repository: FundBuyRepo val sipDetailsResponse: LiveData get() = _sipDetailsResponse + private val _paymentModeNetBanking = MutableLiveData(false) + val paymentModeNetBanking: LiveData + get() = _paymentModeNetBanking + + val paymentMode: MutableLiveData = MutableLiveData(null) + var isSipSelected: Boolean? = null var toHideDate: Boolean = false var isSipTutorialCrossed: Boolean = false @@ -85,6 +92,11 @@ class FundBuyV2ViewModel @Inject constructor(private val repository: FundBuyRepo var isAmountErrorState: Boolean = false var isSipFundReturnNegative: Boolean = false var isSipErrorState: Boolean = false + var maxMandateLimit: String? = null + var sipDateId: String? = null + var paymentModeLimit: String? = null + var weeklyDefaultSipDateText: String? = null + var monthlyDefaultSipDateText: String? = null private val _paymentInitiateData = MutableLiveData?>() @@ -125,6 +137,36 @@ class FundBuyV2ViewModel @Inject constructor(private val repository: FundBuyRepo } } + fun getFundBuyScreenDataV2( + isin: String, + confinedInvestmentType: String? = null, + orderAmount: String? = null, + defaultChecked: String? = null, + screenName: String + ) { + viewModelScope.safeLaunch { + val response = + repository.getFundBuyDetailV2( + isin, + confinedInvestmentType, + orderAmount, + defaultChecked, + metricInfo = getAmcMetricInfo(screenName = screenName) + ) + if (response.error == null && response.errors.isNullOrEmpty()) { + _fundBuyScreenData.value = response.data + } else { + setErrorData(response.errors, response.error) + } + } + } + + fun checkPaymentModeNetBanking() { + if (sipAmount.toDoubleWithSafe() > paymentModeLimit.toDoubleWithSafe()) { + _paymentModeNetBanking.value = true + } + } + fun postSipDetails(details: SipDetailsData) { viewModelScope.launch { val response = repository.postSipDetails(details) @@ -404,6 +446,21 @@ class FundBuyV2ViewModel @Inject constructor(private val repository: FundBuyRepo return null } + fun setPaymentMode(it: ActionData) { + paymentMode.value = + it.parameters + ?.firstOrNull { it.key?.contains(Constant.PAYMENT_MODE, true) == true } + ?.value ?: paymentMode.value + } + + fun getPaymentModeText(): String { + return when (paymentMode.value) { + Constant.UPI -> "UPI" + Constant.NET_BANKING -> "Netbanking" + else -> "UPI" + } + } + companion object { private const val DAY_IN_MILLIS = 24 * 60 * 60 * 1000L } diff --git a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/views/AmountChipGroupView.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/views/AmountChipGroupView.kt index 3c8f670191..d6848a1f1c 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/views/AmountChipGroupView.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/views/AmountChipGroupView.kt @@ -11,8 +11,11 @@ import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater import android.view.View -import android.widget.HorizontalScrollView +import android.view.ViewGroup +import android.view.ViewTreeObserver +import android.widget.LinearLayout import android.widget.TextView +import androidx.core.view.updateLayoutParams import androidx.databinding.DataBindingUtil import com.navi.amc.R import com.navi.amc.databinding.AmountChipGroupViewBinding @@ -24,7 +27,7 @@ import com.navi.base.utils.orFalse import com.navi.design.utils.* class AmountChipGroupView(context: Context, attributeSet: AttributeSet? = null) : - HorizontalScrollView(context, attributeSet) { + LinearLayout(context, attributeSet) { private val binding: AmountChipGroupViewBinding private val inflater = LayoutInflater.from(context) private var items: List? = null @@ -54,6 +57,8 @@ class AmountChipGroupView(context: Context, attributeSet: AttributeSet? = null) binding.group.addView(childBinding.root) } + updateChipsWidth(binding.group) + unSelectAllChips() items?.forEachIndexed { index, chipData -> if (chipData?.selected.orFalse()) { @@ -62,6 +67,37 @@ class AmountChipGroupView(context: Context, attributeSet: AttributeSet? = null) } } + fun updateChipData(items: List) { + this.items = items + unSelectAllChips() + for (i in 0 until binding.group.childCount) { + val chipData = items?.get(i) + binding.group.getChildAt(i).let { chipView -> + chipView.findViewById(R.id.title)?.setSpannableString(chipData?.title) + chipView.findViewById(R.id.label)?.let { labelTv -> + labelTv.setSpannableString(chipData?.label?.title) + labelTv.background = + getNaviDrawable( + radii = CornerRadius(leftBottom = dpToPx(4), rightBottom = dpToPx(4)), + backgroundColor = + chipData + ?.label + ?.bgColor + .parseColorSafe(ColorUtils.DEFAULT_COLOR_VARIATION_UNSELECTED) + ) + } + chipView.setOnClickListener { + if (chipData != null) { + it.setAsSelected(chipData) + } + onSelectedAction?.let { + chipData?.key?.let { key -> it(key, chipData.actionData) } + } + } + } + } + } + private fun View.setAsSelected(chipData: AmountChipData) { unSelectAllChips() background = @@ -126,4 +162,35 @@ class AmountChipGroupView(context: Context, attributeSet: AttributeSet? = null) } unSelectAllChips() } + + private fun updateChipsWidth(parentBinding: ViewGroup) { + val screenWidth = resources.displayMetrics.widthPixels + val screenDensity = resources.displayMetrics.density + + parentBinding.viewTreeObserver.addOnGlobalLayoutListener( + object : ViewTreeObserver.OnGlobalLayoutListener { + + override fun onGlobalLayout() { + parentBinding.viewTreeObserver.removeOnGlobalLayoutListener(this) + + val locationOnScreen = IntArray(2) + parentBinding.getLocationOnScreen(locationOnScreen) + val xCoordinateOfFirstChip = locationOnScreen[0] + val widthOfEachChip = + (screenWidth - 2 * xCoordinateOfFirstChip - (16 * screenDensity)) / 3 + + for (i in 0 until parentBinding.childCount) { + val chip = parentBinding.getChildAt(i) + chip?.updateLayoutParams { + width = widthOfEachChip.toInt() + } + } + } + } + ) + } + + fun refreshChipWidth(recommended: AmountChipGroupView) { + updateChipsWidth(recommended.binding.group) + } } diff --git a/android/navi-amc/src/main/java/com/navi/amc/network/retrofit/RetrofitService.kt b/android/navi-amc/src/main/java/com/navi/amc/network/retrofit/RetrofitService.kt index 383cf2164a..1d0bbfb8bd 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/network/retrofit/RetrofitService.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/network/retrofit/RetrofitService.kt @@ -22,6 +22,7 @@ import com.navi.amc.utils.Constant.FUND_LIST_DATA_V2 import com.navi.amc.utils.Constant.FUND_LIST_DATA_V3 import com.navi.base.model.ActionData import com.navi.common.csat.models.NetPromoterScoreRequest +import com.navi.common.model.GenericBottomSheetData import com.navi.common.model.UploadDataAsyncResponse import com.navi.common.network.models.GenericResponse import com.navi.common.network.models.SuccessResponse @@ -231,7 +232,8 @@ interface RetrofitService { @GET("/autopay/{requestId}/status") suspend fun fetchAsyncRequestAutoPayDataPayment( @Path("requestId") requestId: String, - @Query("autopay_type") autopayType: String? = null + @Query("autopay_type") autopayType: String? = null, + @Query("tranReferenceId") tranReferenceId: String? = null ): Response>> @GET("/amc/esign/{requestId}") @@ -378,6 +380,13 @@ interface RetrofitService { @Query("defaultChecked") defaultChecked: String? = null ): Response> + @GET("/fund/purchase-start-page/v2") + suspend fun fetchFundBuyDetailsV2( + @Query("isin") isin: String, + @Query("orderAmount") orderAmount: String? = null, + @Query("defaultChecked") defaultChecked: String? = null + ): Response> + @GET("/fund/lumpsum-purchase-start-page") suspend fun fetchFundLumpsumBuyDetails( @Query("isin") isin: String @@ -414,11 +423,26 @@ interface RetrofitService { @Body data: AutoPaySetupRequestData ): Response>> + @POST("/autopay/create-autopay/v2") + suspend fun postAutoPayDataV2( + @Body data: AutoPaySetupRequestData + ): Response>> + + @POST("/autopay/sip-mandate-setup/token") + suspend fun postMandateWithSipData( + @Body data: MandateWithSipRequestData + ): Response>> + @POST("/sip/create-sip") suspend fun postSipDetails( @Body sipDetails: SipDetailsData ): Response> + @POST("/sip/sip-summary") + suspend fun getSipOrderSummary( + @Body sipOrderSummaryData: SipOrderSummaryData + ): Response> + @GET("static/fund-info") suspend fun fetchFundInformation(): Response> diff --git a/android/navi-amc/src/main/java/com/navi/amc/portfolio/fragments/ModifySipDetailsFragment.kt b/android/navi-amc/src/main/java/com/navi/amc/portfolio/fragments/ModifySipDetailsFragment.kt index e750cdf2ee..261bd5682b 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/portfolio/fragments/ModifySipDetailsFragment.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/portfolio/fragments/ModifySipDetailsFragment.kt @@ -14,30 +14,44 @@ import android.view.View import android.view.ViewStub import androidx.core.view.isVisible import androidx.databinding.DataBindingUtil +import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import com.navi.amc.R +import com.navi.amc.common.activity.CheckerActivity import com.navi.amc.common.fragment.AmcBaseFragment import com.navi.amc.common.fragment.AmcCommonBottomSheet +import com.navi.amc.common.model.SipOrderSummaryData import com.navi.amc.common.taskProcessor.AmcTaskManager +import com.navi.amc.common.viewmodel.PaymentSharedVM import com.navi.amc.databinding.ModifySipDetailsLayoutBinding import com.navi.amc.fundbuy.fragments.SipCalendarFragment +import com.navi.amc.fundbuy.models.AutoPaySetupRequestData import com.navi.amc.portfolio.models.ModifySipRequestDetails import com.navi.amc.portfolio.viewmodels.ModifySipDetailsViewModel import com.navi.amc.utils.AmcAnalytics +import com.navi.amc.utils.AmcAnalytics.AMC_RECEIVED_NULL_POST_PAYMENT_DATA import com.navi.amc.utils.AmcAnalytics.ISIN import com.navi.amc.utils.Constant import com.navi.amc.utils.Constant.ACTION_PERFORMED import com.navi.amc.utils.Constant.AMOUNT import com.navi.amc.utils.Constant.AUTOPAY_LIMIT_MODIFIED +import com.navi.amc.utils.Constant.AUTOPAY_TYPE +import com.navi.amc.utils.Constant.BANK_DETAILS_REF_ID import com.navi.amc.utils.Constant.DATA +import com.navi.amc.utils.Constant.DISMISS import com.navi.amc.utils.Constant.FREQUENCY +import com.navi.amc.utils.Constant.GET_SIP_SUMMARY_BOTTOMSHEET import com.navi.amc.utils.Constant.MONTHLY +import com.navi.amc.utils.Constant.ORDER_ID +import com.navi.amc.utils.Constant.REQUEST_CONFIG import com.navi.amc.utils.Constant.SHOW_BOTTOMSHEET import com.navi.amc.utils.Constant.SIP_DATE import com.navi.amc.utils.Constant.SIP_REFERENCE_ID +import com.navi.amc.utils.Constant.TRANSACTION_ID import com.navi.amc.utils.SubPageStatusType import com.navi.amc.utils.dateFormat import com.navi.amc.utils.getBottomSheet +import com.navi.amc.utils.getPaymentSyncFlowStatusCta import com.navi.base.model.ActionData import com.navi.base.model.LineItem import com.navi.base.model.NaviClickAction @@ -47,6 +61,8 @@ import com.navi.base.utils.isNotNullAndNotEmpty import com.navi.base.utils.orFalse import com.navi.common.listeners.FragmentInterchangeListener import com.navi.common.listeners.HeaderInteractionListener +import com.navi.common.model.AmcBottomSheetData +import com.navi.common.ui.activity.BaseActivity import com.navi.common.ui.fragment.SingleSelectionBottomSheet import com.navi.common.utils.Constants.TRUE import com.navi.common.utils.SPACE @@ -60,7 +76,11 @@ import com.navi.design.utils.setSpannableString import com.navi.naviwidgets.callbacks.WidgetCallback import com.navi.naviwidgets.models.SingleSelectionBottomSheetData import com.navi.naviwidgets.utils.convertObjectToJsonString +import com.navi.naviwidgets.utils.setButtonLoaderState import com.navi.naviwidgets.widgets.textwithsearch.TextSearchClickAction +import com.navi.payment.model.initiatesdk.PaymentPrefetchMethodRequest +import com.navi.payment.utils.PaymentAnalytics +import com.navi.paymentclients.viewmodel.base.PaymentManager import dagger.hilt.android.AndroidEntryPoint import java.util.Calendar @@ -68,6 +88,10 @@ import java.util.Calendar class ModifySipDetailsFragment() : AmcBaseFragment(), WidgetCallback { private val viewModel by viewModels() private lateinit var binding: ModifySipDetailsLayoutBinding + private val paymentSharedVM: PaymentSharedVM by activityViewModels() + private val paymentVM: PaymentManager by activityViewModels() + private var orderSummaryBottomSheetList: List? = null + private var nextCtaText: String? = null override val screenName: String get() = SubPageStatusType.SIP_DETAILS_MODIFY @@ -141,12 +165,84 @@ class ModifySipDetailsFragment() : AmcBaseFragment(), WidgetCallback { binding.footer.root.isVisible = true binding.footer.btn.apply { text = action.title + nextCtaText = action.title setTextColor(action.titleColor.parseColorSafe()) backgroundTintList = ColorStateList.valueOf(action.bgColor.parseColorSafe()) setOnClickListener { view -> validation(it.footer.nextCta) } } } ?: run { binding.footer.root.isVisible = false } } + + buyFlowVM.sipOrderSummaryResponse.observe(viewLifecycleOwner) { + updateLoadingState(false) + it?.let { + orderSummaryBottomSheetList = it.bottomSheetsData + handleMultiBottomSheetAction( + it.actionData, + orderSummaryBottomSheetList, + listener = { increaseAutoPayLimitForSip(it) } + ) + } + } + + buyFlowVM.autoPayPaymentInitiateData.observe(viewLifecycleOwner) { + sendEvent( + AmcAnalytics.AMC_PAYMENT_TOKEN_RECEIVED, + hashMapOf( + PaymentAnalytics.SYNC to it?.syncFlow?.orFalse().toString(), + PaymentAnalytics.IS_TOKEN_VALID to + it?.tokenDetails?.naviSdkToken.isNotNullAndNotEmpty().toString() + ) + ) + hideLoader() + if (it?.syncFlow.orFalse()) { + paymentSharedVM.isSyncFlow = true + if (it?.tokenDetails?.naviSdkToken.isNotNullAndNotEmpty()) { + paymentSharedVM.syncFlowPaymentInitStartTime = System.currentTimeMillis() + sendEvent(AmcAnalytics.AMC_INIT_PAYMENT) + paymentVM.clear() + paymentVM.initActivity(activity = requireActivity()) + paymentVM.getPaymentMethodsV2( + it?.tokenDetails?.naviSdkToken!!, + PaymentPrefetchMethodRequest() + ) + } + } + } + + paymentSharedVM.postPaymentStatus.observe(viewLifecycleOwner) { data -> + data?.let { + val url = + getPaymentSyncFlowStatusCta(CheckerActivity.AUTO_PAY_PAYMENT_CALLBACK_SYNC) + val bundle = + Bundle().apply { + putString( + TRANSACTION_ID, + buyFlowVM.autoPayPaymentInitiateData.value?.tokenDetails?.transactionId + ) + putString( + Constant.NAVI_SDK_TOKEN, + buyFlowVM.autoPayPaymentInitiateData.value?.tokenDetails?.naviSdkToken + ) + putParcelable( + REQUEST_CONFIG, + buyFlowVM.autoPayPaymentInitiateData.value?.requestConfig + ) + putString(PaymentAnalytics.SYNC, PaymentAnalytics.TRUE) + putString( + ORDER_ID, + buyFlowVM.autoPayPaymentInitiateData.value?.tokenDetails?.naviOrderId + ) + putString(SIP_REFERENCE_ID, arguments?.getString(SIP_REFERENCE_ID)) + putString(PaymentAnalytics.PROVIDER, it?.provider) + putParcelable(Constant.PAYMENT_DATA, it) + putAll(arguments) + } + popThisFromBackStack() + fragmentInterchangeListener?.navigateToNextScreen(ActionData(url = url), bundle) + } ?: run { sendEvent(AMC_RECEIVED_NULL_POST_PAYMENT_DATA) } + } + viewModel.success.observe(viewLifecycleOwner) { it?.let { AmcTaskManager.onPrefetchTaskRequired( @@ -161,6 +257,31 @@ class ModifySipDetailsFragment() : AmcBaseFragment(), WidgetCallback { } } + private fun increaseAutoPayLimitForSip(action: ActionData?) { + if (action?.url == DISMISS) { + // do nothing + return + } + val nextCtaUrl = action?.url.orEmpty() + val amount = action?.parameters?.firstOrNull { it.key == AMOUNT }?.value.orEmpty() + val paymentMode = buyFlowVM.paymentMode.value + val bankDetailsRefId = + action?.parameters?.firstOrNull { it.key == BANK_DETAILS_REF_ID }?.value + val sipReferenceId = arguments?.getString(SIP_REFERENCE_ID).orEmpty() + + val autoPaySetupRequestData = + AutoPaySetupRequestData( + bankAccountRefId = bankDetailsRefId, + mandateType = paymentMode, + amount = amount, + sipReferenceId = sipReferenceId + ) + buyFlowVM.postAutoPaySetupRequestDataV2( + autoPaySetupRequestData = autoPaySetupRequestData, + screenName = screenName + ) + } + private fun navigate(action: ActionData) { if (action.url?.contains(SubPageStatusType.AUTO_PAY_SETUP_V2).orFalse()) { arguments?.apply { @@ -168,8 +289,27 @@ class ModifySipDetailsFragment() : AmcBaseFragment(), WidgetCallback { putString(FREQUENCY, viewModel.sipFrequency) putString(SIP_DATE, viewModel.selectedDate) putString(AUTOPAY_LIMIT_MODIFIED, TRUE) - putString(AmcAnalytics.ISIN, viewModel.isin) + putString(ISIN, viewModel.isin) } + } else if (action.url?.equals(GET_SIP_SUMMARY_BOTTOMSHEET).orFalse()) { + arguments?.putString(AUTOPAY_LIMIT_MODIFIED, TRUE) + arguments?.putString(AUTOPAY_TYPE, "DETAILS_MODIFIED") + arguments?.putString(AMOUNT, binding.sipAmount.getUserInputPostValidation()) + arguments?.putString(FREQUENCY, viewModel.sipFrequency) + arguments?.putString(SIP_DATE, viewModel.selectedDate) + val sipOrderSummary = + SipOrderSummaryData( + screenName = screenName, + sipReferenceId = arguments?.getString(SIP_REFERENCE_ID).orEmpty(), + amount = binding.sipAmount.getUserInputPostValidation(), + sipEditNextInstallmentDate = viewModel.selectedDate, + ) + updateLoadingState(true) + buyFlowVM.getSipOrderSummaryData( + sipOrderSummaryData = sipOrderSummary, + screenName = screenName + ) + return } fragmentInterchangeListener?.navigateToNextScreen(action, arguments ?: Bundle()) } @@ -243,6 +383,21 @@ class ModifySipDetailsFragment() : AmcBaseFragment(), WidgetCallback { } } + private fun updateLoadingState(isLoading: Boolean) { + updateButtonLoaderState(isLoading) + if (isLoading) { + (activity as? BaseActivity)?.blockInteractability() + } else { + (activity as? BaseActivity)?.unblockInteractability() + } + } + + private fun updateButtonLoaderState(state: Boolean) { + nextCtaText?.let { + setButtonLoaderState(binding.footer.btn, binding.footer.buttonLoader, state, it) + } + } + private fun showBottomSheet() { val data = convertObjectToJsonString( diff --git a/android/navi-amc/src/main/java/com/navi/amc/portfolio/fragments/SipFragment.kt b/android/navi-amc/src/main/java/com/navi/amc/portfolio/fragments/SipFragment.kt index bcdaf399ec..d49d63807a 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/portfolio/fragments/SipFragment.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/portfolio/fragments/SipFragment.kt @@ -16,17 +16,22 @@ import android.view.View import android.view.ViewStub import androidx.core.view.isVisible import androidx.databinding.DataBindingUtil +import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import com.navi.amc.R +import com.navi.amc.common.activity.CheckerActivity import com.navi.amc.common.fragment.AmcBaseFragment import com.navi.amc.common.model.EmptyProductData import com.navi.amc.common.model.InformationCardData +import com.navi.amc.common.model.SipOrderSummaryData import com.navi.amc.common.taskProcessor.AmcTaskManager import com.navi.amc.common.taskProcessor.SipListPrefetchTask import com.navi.amc.common.view.InformationView +import com.navi.amc.common.viewmodel.PaymentSharedVM import com.navi.amc.databinding.OverviewLayoutBinding import com.navi.amc.databinding.SipCardLayoutBinding import com.navi.amc.databinding.SipScreenLayoutBinding +import com.navi.amc.fundbuy.models.AutoPaySetupRequestData import com.navi.amc.portfolio.models.AutoDebitFailedData import com.navi.amc.portfolio.models.AutoPayCardData import com.navi.amc.portfolio.models.OverviewCardData @@ -34,15 +39,31 @@ import com.navi.amc.portfolio.viewmodels.SipViewModel import com.navi.amc.portfolio.views.AutoPayCardView import com.navi.amc.portfolio.views.AutoPayDebitFailedCard import com.navi.amc.utils.AmcAnalytics +import com.navi.amc.utils.AmcAnalytics.AMC_RECEIVED_NULL_POST_PAYMENT_DATA import com.navi.amc.utils.ColorUtils import com.navi.amc.utils.Constant import com.navi.amc.utils.Constant.ACTION_PERFORMED +import com.navi.amc.utils.Constant.AMOUNT +import com.navi.amc.utils.Constant.BANK_DETAILS_REF_ID +import com.navi.amc.utils.Constant.DISMISS +import com.navi.amc.utils.Constant.GET_SIP_SUMMARY_BOTTOMSHEET +import com.navi.amc.utils.Constant.ORDER_ID +import com.navi.amc.utils.Constant.REQUEST_CONFIG import com.navi.amc.utils.Constant.SOURCE +import com.navi.amc.utils.Constant.TRANSACTION_ID +import com.navi.amc.utils.getPaymentSyncFlowStatusCta import com.navi.base.model.ActionData +import com.navi.base.utils.isNotNullAndNotEmpty +import com.navi.base.utils.orFalse import com.navi.common.listeners.FragmentInterchangeListener +import com.navi.common.model.AmcBottomSheetData +import com.navi.common.ui.activity.BaseActivity import com.navi.design.utils.* import com.navi.naviwidgets.extensions.getGradientColors import com.navi.naviwidgets.extensions.showWhenDataIsAvailable +import com.navi.payment.model.initiatesdk.PaymentPrefetchMethodRequest +import com.navi.payment.utils.PaymentAnalytics +import com.navi.paymentclients.viewmodel.base.PaymentManager import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -51,6 +72,10 @@ class SipFragment() : AmcBaseFragment() { private val viewModel by viewModels() private val myInvestmentsSharedViewModel by viewModels({ requireParentFragment() }) + private val paymentVM: PaymentManager by activityViewModels() + private val paymentSharedVM: PaymentSharedVM by activityViewModels() + private var autoPayView: AutoPayCardView? = null + private var orderSummaryBottomSheetList: List? = null override val screenName: String get() = AmcAnalytics.SIP_INIT_MY_INVESTMENT @@ -114,6 +139,7 @@ class SipFragment() : AmcBaseFragment() { is AutoPayCardData -> { autopay_view = context?.let { it -> AutoPayCardView(it) } autopay_view?.setProperties(it, ::navigate) + autoPayView = autopay_view } is OverviewCardData -> { view = setOverViewCardData(it) @@ -165,6 +191,18 @@ class SipFragment() : AmcBaseFragment() { } } + buyFlowVM.sipOrderSummaryResponse.observe(viewLifecycleOwner) { + updateLoadingState(false) + it?.let { + orderSummaryBottomSheetList = it.bottomSheetsData + handleMultiBottomSheetAction( + it.actionData, + orderSummaryBottomSheetList, + listener = { initiateAutoPayForAllSips(it) } + ) + } + } + viewModel.fetchingFromRemote.observe(viewLifecycleOwner) { if (it) { myInvestmentsSharedViewModel.sipApiCallStarted() @@ -173,11 +211,105 @@ class SipFragment() : AmcBaseFragment() { } } } + + viewModel.autoPayPaymentInitiateData.observe(viewLifecycleOwner) { + sendEvent( + AmcAnalytics.AMC_PAYMENT_TOKEN_RECEIVED, + hashMapOf( + PaymentAnalytics.SYNC to it?.syncFlow?.orFalse().toString(), + PaymentAnalytics.IS_TOKEN_VALID to + it?.tokenDetails?.naviSdkToken.isNotNullAndNotEmpty().toString() + ) + ) + hideLoader() + if (it?.syncFlow.orFalse()) { + paymentSharedVM.isSyncFlow = true + if (it?.tokenDetails?.naviSdkToken.isNotNullAndNotEmpty()) { + paymentSharedVM.syncFlowPaymentInitStartTime = System.currentTimeMillis() + sendEvent(AmcAnalytics.AMC_INIT_PAYMENT) + paymentVM.clear() + paymentVM.initActivity(activity = requireActivity()) + paymentVM.getPaymentMethodsV2( + it?.tokenDetails?.naviSdkToken!!, + PaymentPrefetchMethodRequest() + ) + } + } + } + + paymentSharedVM.postPaymentStatus.observe(viewLifecycleOwner) { data -> + data?.let { + val url = + getPaymentSyncFlowStatusCta(CheckerActivity.AUTO_PAY_PAYMENT_CALLBACK_SYNC) + val bundle = + Bundle().apply { + putString( + TRANSACTION_ID, + viewModel.autoPayPaymentInitiateData.value?.tokenDetails?.transactionId + ) + putString( + Constant.NAVI_SDK_TOKEN, + viewModel.autoPayPaymentInitiateData.value?.tokenDetails?.naviSdkToken + ) + putParcelable( + REQUEST_CONFIG, + viewModel.autoPayPaymentInitiateData.value?.requestConfig + ) + putString(PaymentAnalytics.SYNC, PaymentAnalytics.TRUE) + putString( + ORDER_ID, + viewModel.autoPayPaymentInitiateData.value?.tokenDetails?.naviOrderId + ) + putString(PaymentAnalytics.PROVIDER, it?.provider) + putParcelable(Constant.PAYMENT_DATA, it) + putAll(arguments) + } + fragmentInterchangeListener?.navigateToNextScreen(ActionData(url = url), bundle) + } ?: run { sendEvent(AMC_RECEIVED_NULL_POST_PAYMENT_DATA) } + } } private fun navigate(action: ActionData) { sendEvent(action.metaData?.clickedData) - fragmentInterchangeListener?.navigateToNextScreen(action) + if (action.url == GET_SIP_SUMMARY_BOTTOMSHEET) { + val sipOrderSummary = SipOrderSummaryData(screenName = screenName) + updateLoadingState(true) + buyFlowVM.getSipOrderSummaryData( + sipOrderSummaryData = sipOrderSummary, + screenName = screenName + ) + } else { + fragmentInterchangeListener?.navigateToNextScreen(action) + } + } + + private fun initiateAutoPayForAllSips(action: ActionData?) { + if (action?.url == DISMISS) { + // do nothing + return + } + val nextCtaUrl = action?.url.orEmpty() + val paymentMode = buyFlowVM.paymentMode.value + val amount = action?.parameters?.firstOrNull { it.key == AMOUNT }?.value + val bankDetailsRefId = + action?.parameters?.firstOrNull { it.key == BANK_DETAILS_REF_ID }?.value + + val autoPaySetupRequestData = + AutoPaySetupRequestData( + bankAccountRefId = bankDetailsRefId, + mandateType = paymentMode, + amount = amount + ) + viewModel.postAutoPaySetupRequestData(autoPaySetupRequestData) + } + + private fun updateLoadingState(isLoading: Boolean) { + autoPayView?.updateButtonLoaderState(isLoading) + if (isLoading) { + (activity as? BaseActivity)?.blockInteractability() + } else { + (activity as? BaseActivity)?.unblockInteractability() + } } private fun setOverViewCardData(data: OverviewCardData): View { diff --git a/android/navi-amc/src/main/java/com/navi/amc/portfolio/fragments/SipModifyFragment.kt b/android/navi-amc/src/main/java/com/navi/amc/portfolio/fragments/SipModifyFragment.kt index 73f67a2371..9bf60e6822 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/portfolio/fragments/SipModifyFragment.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/portfolio/fragments/SipModifyFragment.kt @@ -12,19 +12,23 @@ import android.os.Bundle import android.view.View import android.view.ViewStub import android.widget.LinearLayout.LayoutParams +import androidx.core.view.allViews import androidx.core.view.updateLayoutParams import androidx.databinding.DataBindingUtil import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import com.google.gson.reflect.TypeToken import com.navi.amc.R +import com.navi.amc.common.activity.CheckerActivity import com.navi.amc.common.fragment.AmcBaseFragment import com.navi.amc.common.listener.FooterInteractionListener import com.navi.amc.common.model.InformationCardData +import com.navi.amc.common.model.SipOrderSummaryData import com.navi.amc.common.taskProcessor.AmcTaskManager import com.navi.amc.common.view.InformationView import com.navi.amc.common.viewmodel.PaymentSharedVM import com.navi.amc.databinding.FragmentSipModificationBinding +import com.navi.amc.fundbuy.models.AutoPaySetupRequestData import com.navi.amc.fundbuy.models.SipDetailsData import com.navi.amc.portfolio.models.AutoPayCardData import com.navi.amc.portfolio.models.SingleProductData @@ -33,15 +37,19 @@ import com.navi.amc.portfolio.viewmodels.SipModificationVM import com.navi.amc.portfolio.views.AutoPayCardView import com.navi.amc.portfolio.views.ProductDetailInformationLayout import com.navi.amc.utils.AmcAnalytics +import com.navi.amc.utils.AmcAnalytics.AMC_RECEIVED_NULL_POST_PAYMENT_DATA import com.navi.amc.utils.AmcAnalytics.ISIN import com.navi.amc.utils.Constant import com.navi.amc.utils.Constant.ACTION_PERFORMED import com.navi.amc.utils.Constant.AMOUNT +import com.navi.amc.utils.Constant.BANK_DETAILS_REF_ID import com.navi.amc.utils.Constant.BYPASS_OTP import com.navi.amc.utils.Constant.DELETED import com.navi.amc.utils.Constant.DELETE_SIP_WITH_REASON +import com.navi.amc.utils.Constant.DISMISS import com.navi.amc.utils.Constant.FLOW_TYPE_SIP_PURCHASE import com.navi.amc.utils.Constant.FORMATTED_AMOUNT +import com.navi.amc.utils.Constant.GET_SIP_SUMMARY_BOTTOMSHEET import com.navi.amc.utils.Constant.MANDATE_OPTED_IN import com.navi.amc.utils.Constant.MANDATE_OPTED_OUT import com.navi.amc.utils.Constant.MODIFY_BOTTOM_SHEET @@ -59,6 +67,7 @@ import com.navi.amc.utils.Constant.TRANSACTION_ID import com.navi.amc.utils.SubPageStatusType import com.navi.amc.utils.bundleToMap import com.navi.amc.utils.getJsonObject +import com.navi.amc.utils.getPaymentSyncFlowStatusCta import com.navi.amc.utils.showToastMessage import com.navi.base.model.ActionData import com.navi.base.model.CtaData @@ -68,6 +77,7 @@ import com.navi.common.csat.CsatBottomSheet import com.navi.common.csat.models.NetPromoterScoreRequest import com.navi.common.listeners.FragmentInterchangeListener import com.navi.common.listeners.HeaderInteractionListener +import com.navi.common.model.AmcBottomSheetData import com.navi.common.model.ModuleNameV2 import com.navi.common.network.models.GenericErrorResponse import com.navi.common.ui.activity.BaseActivity @@ -89,6 +99,7 @@ import com.navi.payment.model.initiatesdk.PaymentPrefetchMethodRequest import com.navi.payment.nativepayment.sharedviewmodel.NaviCheckoutViewModel import com.navi.payment.utils.PaymentAnalytics import com.navi.payment.utils.PaymentSource +import com.navi.paymentclients.viewmodel.base.PaymentManager import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -97,7 +108,10 @@ class SipModifyFragment : AmcBaseFragment(), FooterInteractionListener { private val viewModel by viewModels() private var bottomSheetData: GenericErrorResponse? = null private val paymentSharedVM: PaymentSharedVM by activityViewModels() + private val paymentVM: PaymentManager by activityViewModels() private val naviCheckoutViewModel by activityViewModels() + private var orderSummaryBottomSheetList: List? = null + private var isAutoPayCardClicked: Boolean? = null override val screenName: String get() = SubPageStatusType.SIP_MODIFICATION @@ -191,9 +205,7 @@ class SipModifyFragment : AmcBaseFragment(), FooterInteractionListener { showToastMessage(context, toastMessage) sendEvent( AmcAnalytics.AMC_INIT_SIP_DETAILS_SIP_SUCCESS_TOAST, - hashMapOf( - AmcAnalytics.FUND_ID to arguments?.getString(AmcAnalytics.ISIN).orEmpty() - ) + hashMapOf(AmcAnalytics.FUND_ID to arguments?.getString(ISIN).orEmpty()) ) response?.content?.toastMessage = null // so that we wont show the same toast again } @@ -222,7 +234,22 @@ class SipModifyFragment : AmcBaseFragment(), FooterInteractionListener { buyFlowVM.startPaymentFlow.observeNonNull(viewLifecycleOwner) { if (it == true) { buyFlowVM.setStartPaymentFlow(false) - updateLoadingState(false) + updateLoadingState(false, false) + } + } + + buyFlowVM.sipOrderSummaryResponse.observe(viewLifecycleOwner) { + isAutoPayCardClicked?.let { it1 -> + updateLoadingState(false, it1) + isAutoPayCardClicked = null + } + it?.let { + orderSummaryBottomSheetList = it.bottomSheetsData + handleMultiBottomSheetAction( + it.actionData, + orderSummaryBottomSheetList, + listener = { initiateAutoPayForSip(it) } + ) } } @@ -278,7 +305,65 @@ class SipModifyFragment : AmcBaseFragment(), FooterInteractionListener { } } } - viewModel.errorResponse.observe(viewLifecycleOwner) { updateLoadingState(false) } + + viewModel.errorResponse.observe(viewLifecycleOwner) { updateLoadingState(false, false) } + + buyFlowVM.autoPayPaymentInitiateData.observe(viewLifecycleOwner) { + sendEvent( + AmcAnalytics.AMC_PAYMENT_TOKEN_RECEIVED, + hashMapOf( + PaymentAnalytics.SYNC to it?.syncFlow?.orFalse().toString(), + PaymentAnalytics.IS_TOKEN_VALID to + it?.tokenDetails?.naviSdkToken.isNotNullAndNotEmpty().toString() + ) + ) + hideLoader() + if (it?.syncFlow.orFalse()) { + paymentSharedVM.isSyncFlow = true + if (it?.tokenDetails?.naviSdkToken.isNotNullAndNotEmpty()) { + paymentSharedVM.syncFlowPaymentInitStartTime = System.currentTimeMillis() + sendEvent(AmcAnalytics.AMC_INIT_PAYMENT) + paymentVM.clear() + paymentVM.initActivity(activity = requireActivity()) + paymentVM.getPaymentMethodsV2( + it?.tokenDetails?.naviSdkToken!!, + PaymentPrefetchMethodRequest() + ) + } + } + } + + paymentSharedVM.postPaymentStatus.observe(viewLifecycleOwner) { data -> + data?.let { + val url = + getPaymentSyncFlowStatusCta(CheckerActivity.AUTO_PAY_PAYMENT_CALLBACK_SYNC) + val bundle = + Bundle().apply { + putString( + TRANSACTION_ID, + buyFlowVM.autoPayPaymentInitiateData.value?.tokenDetails?.transactionId + ) + putString( + Constant.NAVI_SDK_TOKEN, + buyFlowVM.autoPayPaymentInitiateData.value?.tokenDetails?.naviSdkToken + ) + putParcelable( + REQUEST_CONFIG, + buyFlowVM.autoPayPaymentInitiateData.value?.requestConfig + ) + putString(PaymentAnalytics.SYNC, PaymentAnalytics.TRUE) + putString( + ORDER_ID, + buyFlowVM.autoPayPaymentInitiateData.value?.tokenDetails?.naviOrderId + ) + putString(SIP_REFERENCE_ID, arguments?.getString(SIP_REFERENCE_ID)) + putString(PaymentAnalytics.PROVIDER, it?.provider) + putParcelable(Constant.PAYMENT_DATA, it) + putAll(arguments) + } + fragmentInterchangeListener?.navigateToNextScreen(ActionData(url = url), bundle) + } ?: run { sendEvent(AMC_RECEIVED_NULL_POST_PAYMENT_DATA) } + } } private val primaryClickListener: View.OnClickListener = @@ -291,7 +376,7 @@ class SipModifyFragment : AmcBaseFragment(), FooterInteractionListener { handleOnClick(bottomSheetData?.actions?.getOrNull(1)?.cta?.toActionData()) } - private fun handleOnClick(actionData: ActionData?) { + private fun handleOnClick(actionData: ActionData?, isFooterAction: Boolean? = false) { sendEvent(actionData?.metaData?.clickedData) if ( viewModel.sipDetailsData.value?.content?.genericBottomSheets?.get(actionData?.url) != @@ -339,9 +424,20 @@ class SipModifyFragment : AmcBaseFragment(), FooterInteractionListener { putString(AMOUNT, getParameterValue(FORMATTED_AMOUNT)) putString(ORDER_HEADER_SUBTITLE, getParameterValue(ORDER_HEADER_SUBTITLE)) } - updateLoadingState(true) + updateLoadingState(true, false) viewModel.initiateSipPayment(sipDetailsData) - } else if (actionData?.url.isNullOrEmpty().not()) { + } else if (actionData?.url == GET_SIP_SUMMARY_BOTTOMSHEET) { + val sipOrderSummary = + SipOrderSummaryData( + screenName = screenName, + sipReferenceId = arguments?.getString(SIP_REFERENCE_ID).orEmpty() + ) + updateLoadingState(true, !isFooterAction!!) + buyFlowVM.getSipOrderSummaryData( + sipOrderSummaryData = sipOrderSummary, + screenName = screenName + ) + } else { fragmentInterchangeListener?.navigateToNextScreen(actionData, arguments ?: Bundle()) } } @@ -356,8 +452,18 @@ class SipModifyFragment : AmcBaseFragment(), FooterInteractionListener { .orEmpty() } - private fun updateLoadingState(isLoading: Boolean) { - binding.footerView.updateButtonLoaderState(isLoading) + private fun updateLoadingState(isLoading: Boolean, autoPayCard: Boolean) { + if (autoPayCard) { + isAutoPayCardClicked = true + binding.itemContainer.allViews.forEach { + if (it is AutoPayCardView) { + it.updateButtonLoaderState(isLoading) + } + } + } else { + isAutoPayCardClicked = false + binding.footerView.updateButtonLoaderState(isLoading) + } if (isLoading) { (activity as? BaseActivity)?.blockInteractability() } else { @@ -408,7 +514,32 @@ class SipModifyFragment : AmcBaseFragment(), FooterInteractionListener { } override fun onFooterNextPress(actionData: ActionData?, skipValidation: Boolean?) { - handleOnClick(actionData) + handleOnClick(actionData, true) + } + + private fun initiateAutoPayForSip(action: ActionData?) { + if (action?.url == DISMISS) { + // do nothing + return + } + val nextCtaUrl = action?.url.orEmpty() + val amount = action?.parameters?.firstOrNull { it.key == AMOUNT }?.value.orEmpty() + val paymentMode = buyFlowVM.paymentMode.value + val bankDetailsRefId = + action?.parameters?.firstOrNull { it.key == BANK_DETAILS_REF_ID }?.value + val sipReferenceId = arguments?.getString(SIP_REFERENCE_ID).orEmpty() + + val autoPaySetupRequestData = + AutoPaySetupRequestData( + bankAccountRefId = bankDetailsRefId, + mandateType = paymentMode, + amount = amount, + sipReferenceId = sipReferenceId + ) + buyFlowVM.postAutoPaySetupRequestDataV2( + autoPaySetupRequestData = autoPaySetupRequestData, + screenName = screenName + ) } companion object { diff --git a/android/navi-amc/src/main/java/com/navi/amc/portfolio/repositories/SipDetailsRepository.kt b/android/navi-amc/src/main/java/com/navi/amc/portfolio/repositories/SipDetailsRepository.kt index fbc73db971..fb119b11d1 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/portfolio/repositories/SipDetailsRepository.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/portfolio/repositories/SipDetailsRepository.kt @@ -7,6 +7,9 @@ package com.navi.amc.portfolio.repositories +import com.navi.amc.common.model.AdditionalDataAsyncResponse +import com.navi.amc.common.model.NextCtaResponse +import com.navi.amc.fundbuy.models.AutoPaySetupRequestData import com.navi.amc.network.retrofit.RetrofitService import com.navi.amc.portfolio.models.SipScreenData import com.navi.common.checkmate.model.MetricInfo @@ -16,10 +19,6 @@ import javax.inject.Inject class SipDetailsRepository @Inject constructor(private val retrofitService: RetrofitService) : ResponseCallback() { - /*suspend fun fetchSipDetails() : RepoResult { - val type = object : TypeToken() {}.type - return mockApiResponse(type, "orders_list") - }*/ suspend fun fetchSipDetails( source: Map?, metricInfo: MetricInfo> @@ -28,4 +27,13 @@ class SipDetailsRepository @Inject constructor(private val retrofitService: Retr response = retrofitService.fetchSipDetails(source.orEmpty()), metricInfo = metricInfo ) + + suspend fun postAutoPaySetupRequestData( + data: AutoPaySetupRequestData, + metricInfo: MetricInfo>>? = null + ) = + apiResponseCallback( + response = retrofitService.postAutoPayData(data), + metricInfo = metricInfo + ) } diff --git a/android/navi-amc/src/main/java/com/navi/amc/portfolio/viewmodels/SipViewModel.kt b/android/navi-amc/src/main/java/com/navi/amc/portfolio/viewmodels/SipViewModel.kt index b0cb2a1137..fce0fa01ee 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/portfolio/viewmodels/SipViewModel.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/portfolio/viewmodels/SipViewModel.kt @@ -10,13 +10,18 @@ package com.navi.amc.portfolio.viewmodels import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope +import com.navi.amc.common.model.AdditionalDataAsyncResponse +import com.navi.amc.common.model.NextCtaResponse import com.navi.amc.common.taskProcessor.AmcTaskListener import com.navi.amc.common.taskProcessor.AmcTaskManager import com.navi.amc.common.taskProcessor.SipListPrefetchTask import com.navi.amc.common.viewmodel.BaseAmcVM +import com.navi.amc.fundbuy.models.AutoPaySetupRequestData import com.navi.amc.portfolio.models.SipScreenData import com.navi.amc.portfolio.repositories.SipDetailsRepository import com.navi.amc.utils.getAmcMetricInfo +import com.navi.amc.utils.updateCheckerResponse +import com.navi.common.utils.SingleLiveEvent import com.navi.common.utils.isValidResponse import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @@ -28,6 +33,11 @@ class SipViewModel @Inject constructor(private val repository: SipDetailsReposit val sipDetailsData: LiveData get() = _sipDetailsData + private val _autoPayPaymentInitiateData = + SingleLiveEvent?>() + val autoPayPaymentInitiateData: LiveData?> + get() = _autoPayPaymentInitiateData + fun fetchSipDetails(actionPerformed: Map? = null, screenName: String) { if (!getSipDetailsFromCache()) { if (AmcTaskManager.isTaskActive(SipListPrefetchTask.SIP_LIST_PREFETCH)) { @@ -93,6 +103,18 @@ class SipViewModel @Inject constructor(private val repository: SipDetailsReposit return false } + fun postAutoPaySetupRequestData(autoPaySetupRequestData: AutoPaySetupRequestData) { + // auto pay setup for all sips + viewModelScope.safeLaunch { + val response = repository.postAutoPaySetupRequestData(autoPaySetupRequestData) + if (response.error == null && response.errors.isNullOrEmpty()) { + _autoPayPaymentInitiateData.value = updateCheckerResponse(response.data) + } else { + setErrorData(response.errors, response.error) + } + } + } + override fun onCleared() { errorResponse.value = null _sipDetailsData.value = null diff --git a/android/navi-amc/src/main/java/com/navi/amc/portfolio/views/AutoPayCardView.kt b/android/navi-amc/src/main/java/com/navi/amc/portfolio/views/AutoPayCardView.kt index df0e67ef71..6cd79c09a7 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/portfolio/views/AutoPayCardView.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/portfolio/views/AutoPayCardView.kt @@ -11,6 +11,7 @@ import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.ContextCompat import androidx.databinding.DataBindingUtil import com.navi.amc.R import com.navi.amc.databinding.AutopayCardBinding @@ -21,10 +22,13 @@ import com.navi.design.utils.getNaviDrawable import com.navi.design.utils.parseColorSafe import com.navi.design.utils.setSpannableString import com.navi.naviwidgets.extensions.showWhenDataIsAvailable +import com.navi.naviwidgets.utils.setButtonLoaderStateNonShrinking class AutoPayCardView(context: Context, attributeSet: AttributeSet? = null) : ConstraintLayout(context, attributeSet) { private val binding: AutopayCardBinding + private var buttonText: String? = null + private var nextCtaColor: Int? = null init { val inflater = LayoutInflater.from(context) @@ -36,7 +40,9 @@ class AutoPayCardView(context: Context, attributeSet: AttributeSet? = null) : binding.icon.showWhenDataIsAvailable(data.iconCode) binding.button.apply { text = data.action?.title + buttonText = data.action?.title setTextColor(data.action?.titleColor.parseColorSafe()) + nextCtaColor = data.action?.titleColor.parseColorSafe() background = getNaviDrawable( cornerRadius = dpToPxInInt(4), @@ -54,4 +60,31 @@ class AutoPayCardView(context: Context, attributeSet: AttributeSet? = null) : strokeColor = resources.getColor(R.color.color_ffe6a0) ) } + + fun updateButtonLoaderState(state: Boolean) { + buttonText?.let { + if (state) { + binding.button.background = + getNaviDrawable( + cornerRadius = resources.getDimension(com.navi.design.R.dimen.dp_4).toInt(), + backgroundColor = + ContextCompat.getColor(context, R.color.button_loader_loading_color) + ) + } else { + binding.button.background = + getNaviDrawable( + cornerRadius = resources.getDimension(com.navi.design.R.dimen.dp_4).toInt(), + backgroundColor = + ContextCompat.getColor(context, com.navi.design.R.color.color_1F002A) + ) + } + setButtonLoaderStateNonShrinking( + binding.button, + binding.buttonLoader, + state, + it, + nextCtaColor + ) + } + } } diff --git a/android/navi-amc/src/main/java/com/navi/amc/utils/AmcAnalytics.kt b/android/navi-amc/src/main/java/com/navi/amc/utils/AmcAnalytics.kt index bb7942893a..deff5ec4f8 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/utils/AmcAnalytics.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/utils/AmcAnalytics.kt @@ -47,6 +47,7 @@ object AmcAnalytics { const val INVESTMENT_INIT_LANDING_PAGE = "investments_init_landing_page" const val FUND_DETAILS = "amc_init_mf" const val FUND_BUY_SIP_LUMPSUM_SCREEN = "fund_buy_sip_lumpsum_screen" + const val FUND_BUY_SIP_LUMPSUM_SCREEN_V3 = "fund_buy_sip_lumpsum_screen_v3" const val AMC_INIT_OLD_SIP_LUMPSUM = "amc_init_old_sip_lumpsum" const val FUND_BUY_LUMPSUM_SCREEN = "fund_buy_lumpsum_screen" const val FUND_BUY_SIP_SCREEN = "fund_buy_sip_screen" @@ -268,6 +269,7 @@ object AmcAnalytics { const val AMC_OTP_GENERATE_ERROR_ERROR = "amc_otp_generate_error_response_error" const val AMC_RECEIVED_NULL_SIP_REFERENCE_ID = "amc_received_null_sip_reference_id" + const val AMC_RECEIVED_NULL_POST_PAYMENT_DATA = "amc_received_null_post_payment_data" fun sendEvent( eventsData: GenericAnalyticsData?, diff --git a/android/navi-amc/src/main/java/com/navi/amc/utils/ColorUtils.kt b/android/navi-amc/src/main/java/com/navi/amc/utils/ColorUtils.kt index 9e66f2dbd1..3d6eddad16 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/utils/ColorUtils.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/utils/ColorUtils.kt @@ -29,6 +29,7 @@ object ColorUtils { const val FUND_GRAPH_CHIP_SELECTED_COLOR = "#1F002A" const val RADIO_SELECTED_COLOR = "#191919" const val RADIO_UNSELECTED_COLOR = "#6B6B6B" + const val BUTTON_LOADER_LOADING_COLOR = "#584460" fun getPurpleRoundedDrawable(context: Context) = getNaviDrawable( diff --git a/android/navi-amc/src/main/java/com/navi/amc/utils/Constant.kt b/android/navi-amc/src/main/java/com/navi/amc/utils/Constant.kt index eee176f8ff..d832aa09ae 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/utils/Constant.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/utils/Constant.kt @@ -105,6 +105,7 @@ object Constant { const val NAVI_SDK_TOKEN = "naviSdkToken" const val REQUEST_CONFIG = "requestConfig" const val PAYMENT_DATA = "paymentData" + const val TRANSACTION_REFERENCE_ID = "tranReferenceId" const val INVESTMENT_TYPE_MONTHLY_SIP = "MONTHLY_SIP" const val INVESTMENT_TYPE_ONE_TIME = "ONE_TIME" @@ -181,4 +182,19 @@ object Constant { const val ALL = "ALL" const val DARK_YELLOW_COLOR = "#FFE6A0" const val BACK = "back" + const val FIRST_INSTALLMENT_DATE = "FIRST_INSTALLMENT_DATE" + const val NEXT_INSTALLMENT_DATE = "NEXT_INSTALLMENT_DATE" + const val GET_SIP_SUMMARY_BOTTOMSHEET = "get_sip_summary_bottom_sheet" + const val SETUP_SIP_AUTOPAY = "SETUP_SIP_AUTOPAY" + const val SETUP_AUTOPAY_EXISTING_SIP = "SETUP_AUTOPAY_EXISTING_SIP" + const val SETUP_AUTOPAY_ALL_SIP = "SETUP_AUTOPAY_ALL_SIP" + const val NET_BANKING = "NET_BANKING" + const val CHANGE_PAYMENT_MODE = "CHANGE_PAYMENT_MODE" + const val PAYMENT_MODE_CHANGED = "PAYMENT_MODE_CHANGED" + + const val SIP_INSTALLMENT_CALENDAR_PREFIX = "You will be paying 1st SIP installment " + const val AND_INSTALLMENT = " and installment " + const val SIP_INSTALLMENT_CALENDAR_SUFFIX = " will be auto-deducted" + const val CALENDAR_DATE = "calendarDate" + const val SIP_NEXT_INSTALLMENT_DATE = "sipNextInstallmentDate" } diff --git a/android/navi-amc/src/main/java/com/navi/amc/utils/Ext.kt b/android/navi-amc/src/main/java/com/navi/amc/utils/Ext.kt index ef84a1030e..d96e8fa0ce 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/utils/Ext.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/utils/Ext.kt @@ -188,6 +188,10 @@ fun getFragment(screen: String, bundle: Bundle): Fragment? { bundle.putString(INVESTMENT_TYPE, AmcAnalytics.SIP_AND_LUMPSUM) FundBuyingFragmentV2.newInstance(bundle) } + SubPageStatusType.FUND_BUY_SIP_LUMPSUM_V2 -> { + bundle.putString(INVESTMENT_TYPE, AmcAnalytics.SIP_AND_LUMPSUM) + FundBuyingFragmentV3.newInstance(bundle) + } SubPageStatusType.FUND_BUY_LUMPSUM_SIMPLIFIED -> { bundle.putString(INVESTMENT_TYPE, AmcAnalytics.LUMPSUMP) FundBuyingFragmentV2.newInstance(bundle) @@ -276,6 +280,10 @@ fun getBottomSheet( NudgeBottomSheet.newInstance(bundle = bundle, listener = secondaryListener) SubPageStatusType.AMC_LIST_DETAILS_IN_CARD_BOTTOMSHEET -> AmcListDetailsInCardBottomSheet.newInstance(bundle = bundle, action = genericListener) + SubPageStatusType.AMC_DYNAMIC_BOTTOMSHEET -> + AmcDynamicBottomSheet.newInstance(bundle = bundle, listener, action = genericListener) + SubPageStatusType.AMC_ORDER_SUMMARY_BOTTOMSHEET -> + AmcCommonComposableBottomSheet.newInstance(bundle = bundle) else -> null } } diff --git a/android/navi-amc/src/main/java/com/navi/amc/utils/SubPageStatusType.kt b/android/navi-amc/src/main/java/com/navi/amc/utils/SubPageStatusType.kt index 8e4a37880e..5186ffefcc 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/utils/SubPageStatusType.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/utils/SubPageStatusType.kt @@ -30,6 +30,7 @@ object SubPageStatusType { const val FUND_DETAILS = "fund_details" const val FUND_DETAILS_V2 = "fund_details_v2" const val FUND_BUY_SIP_LUMPSUM = "fund_buy_sip_lumpsum" + const val FUND_BUY_SIP_LUMPSUM_V2 = "fund_buy_sip_lumpsum_v2" const val FUND_BUY_LUMPSUM_SIMPLIFIED = "fund_buy_simplified_lumpsum_ui" const val FUND_BUY_SIP_SIMPLIFIED = "fund_buy_simplified_sip_ui" const val ORDER_STATUS = "order_status" @@ -68,4 +69,7 @@ object SubPageStatusType { const val AMC_QUESTIONNAIRE_PAGE = "amc_questionnaire_page" const val SIP_TYPE = "sip_type" const val AUTO_PAY_SETUP_V2 = "auto_pay_setup_v2" + const val AMC_DYNAMIC_BOTTOMSHEET = "AMC_DYNAMIC_BOTTOMSHEET" + const val AMC_ORDER_SUMMARY_BOTTOMSHEET = "AMC_ORDER_SUMMARY_BOTTOMSHEET" + const val AMC_COMMON_BOTTOMSHEET_V2 = "AMC_COMMON_BOTTOMSHEET_V2" } diff --git a/android/navi-amc/src/main/res/layout/amc_common_bottomsheet.xml b/android/navi-amc/src/main/res/layout/amc_common_bottomsheet.xml index ec48c9b2e9..012729d201 100644 --- a/android/navi-amc/src/main/res/layout/amc_common_bottomsheet.xml +++ b/android/navi-amc/src/main/res/layout/amc_common_bottomsheet.xml @@ -48,18 +48,56 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/title" /> - + + + app:layout_constraintTop_toBottomOf="@id/calendar_widget"> + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/navi-amc/src/main/res/layout/amount_chip_group_view.xml b/android/navi-amc/src/main/res/layout/amount_chip_group_view.xml index 1d8c5c46cf..4625e83fa8 100644 --- a/android/navi-amc/src/main/res/layout/amount_chip_group_view.xml +++ b/android/navi-amc/src/main/res/layout/amount_chip_group_view.xml @@ -2,17 +2,12 @@ - - \ No newline at end of file diff --git a/android/navi-amc/src/main/res/layout/autopay_card.xml b/android/navi-amc/src/main/res/layout/autopay_card.xml index 08694a74e4..b5295d5f56 100644 --- a/android/navi-amc/src/main/res/layout/autopay_card.xml +++ b/android/navi-amc/src/main/res/layout/autopay_card.xml @@ -43,6 +43,20 @@ app:layout_constraintTop_toBottomOf="@id/title" tools:text="Set autopay" /> + + + + - + app:layout_constraintStart_toStartOf="parent" + android:layout_marginBottom="@dimen/dp_32" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/navi-amc/src/main/res/layout/order_status_layout.xml b/android/navi-amc/src/main/res/layout/order_status_layout.xml index 952f922d0a..2afc5be08b 100644 --- a/android/navi-amc/src/main/res/layout/order_status_layout.xml +++ b/android/navi-amc/src/main/res/layout/order_status_layout.xml @@ -82,15 +82,58 @@ app:layout_constraintTop_toTopOf="parent" app:orderDetails="@{response}" /> + + + + + + + + + + + app:layout_constraintTop_toBottomOf="@id/in_progress_widget" /> @@ -117,7 +116,7 @@ android:id="@+id/btn" android:layout_width="@dimen/dp_0" android:layout_height="@dimen/dp_48" - android:layout_marginTop="@dimen/dp_16" + android:layout_marginTop="@dimen/_18dp" android:layout_marginBottom="@dimen/dp_32" android:background="@drawable/bg_cta_primary_purple_amc_rounded_4" android:fontFamily="@font/navi_body_demi_bold" @@ -130,6 +129,20 @@ app:layout_constraintTop_toBottomOf="@id/divider" tools:text="Pay now" /> + + #011A48 #6B6B6B #EBEBEB - + #584460 \ No newline at end of file diff --git a/android/navi-common/src/main/java/com/navi/common/model/AmcBottomSheetData.kt b/android/navi-common/src/main/java/com/navi/common/model/AmcBottomSheetData.kt new file mode 100644 index 0000000000..9b7c29bd31 --- /dev/null +++ b/android/navi-common/src/main/java/com/navi/common/model/AmcBottomSheetData.kt @@ -0,0 +1,65 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.common.model + +import android.os.Parcelable +import com.google.gson.annotations.SerializedName +import com.navi.base.model.ActionData +import com.navi.naviwidgets.models.response.ImageFieldData +import com.navi.naviwidgets.models.response.TextFieldData +import kotlinx.parcelize.Parcelize + +@Parcelize +data class GenericBottomSheetData( + @SerializedName("bottomSheetsData") val bottomSheetsData: List? = null, + @SerializedName("actionData", alternate = ["action"]) val actionData: ActionData? = null +) : Parcelable + +@Parcelize +data class AmcBottomSheetData( + @SerializedName("id") val id: String? = null, + @SerializedName("title") val title: TextFieldData? = null, + @SerializedName("subtitle", alternate = ["subTitle"]) val subtitle: TextFieldData? = null, + @SerializedName("leftIcon") val leftIcon: ImageFieldData? = null, + @SerializedName("centerIcon", alternate = ["centreIcon"]) + val centerIcon: ImageFieldData? = null, + @SerializedName("rightIcon") val rightIcon: ImageFieldData? = null, + @SerializedName("buttonText", alternate = ["primaryButtonText"]) + val buttonText: TextFieldData? = null, + @SerializedName("secondaryButtonText") val secondaryButtonText: TextFieldData? = null, + @SerializedName("imageUrl") val imageUrl: ImageFieldData? = null, + @SerializedName("actionData") val actionData: ActionData? = null, + @SerializedName("itemList") val itemList: List? = null, + @SerializedName("containerList") val containerList: List? = null +) : Parcelable + +@Parcelize +data class PairItems( + @SerializedName("leftItem") val leftItem: TextFieldData? = null, + @SerializedName("separator") val separator: TextFieldData? = null, + @SerializedName("rightItem") val rightItem: TextFieldData? = null +) : Parcelable + +@Parcelize +data class ContainerIem( + @SerializedName("id") val id: String? = null, + @SerializedName("leftIcon") val leftIcon: ImageFieldData? = null, + @SerializedName("leftTitle") val leftTitle: TextFieldData? = null, + @SerializedName("leftSubtitle") val leftSubtitle: TextFieldData? = null, + @SerializedName("rightIcon") val rightIcon: ImageFieldData? = null, + @SerializedName("rightTitle") val rightTitle: TextFieldData? = null, + @SerializedName("actionData") val actionData: ActionData? = null, + @SerializedName("singleSelection") val singleSelection: Boolean = false, + @SerializedName("selectedRightIcon") val selectedRightIcon: ImageFieldData? = null, + @SerializedName("isVisible") var isVisible: Boolean? = false, + @SerializedName("preSelected") var preSelected: Boolean? = false, + @SerializedName("isClickable") var isClickable: Boolean? = true, + @SerializedName("subText") val subText: TextFieldData? = null, + @SerializedName("subTextBgColor") var subTextBgColor: String? = null, + @SerializedName("highlightSelection") var highlightSelection: Boolean? = true +) : Parcelable diff --git a/android/navi-common/src/main/java/com/navi/common/ui/compose/GenericComposableBottomSheet.kt b/android/navi-common/src/main/java/com/navi/common/ui/compose/GenericComposableBottomSheet.kt new file mode 100644 index 0000000000..1bbff85f97 --- /dev/null +++ b/android/navi-common/src/main/java/com/navi/common/ui/compose/GenericComposableBottomSheet.kt @@ -0,0 +1,395 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.common.ui.compose + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.DefaultShadowColor +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.navi.base.model.ActionData +import com.navi.base.model.CtaData +import com.navi.common.model.AmcBottomSheetData +import com.navi.common.model.ContainerIem +import com.navi.common.model.PairItems +import com.navi.common.utils.parseColor +import com.navi.common.utils.toCtaData +import com.navi.common.viewmodel.GenericBottomSheetVM +import com.navi.design.utils.clickableWithNoGesture +import com.navi.naviwidgets.extensions.NaviImage +import com.navi.naviwidgets.extensions.NaviTextWidgetized +import com.navi.naviwidgets.models.FooterButtonState +import com.navi.naviwidgets.models.response.ImageFieldData +import com.navi.naviwidgets.models.response.TextFieldData +import com.navi.uitron.model.ui.ComposePadding +import com.navi.uitron.utils.setPadding + +@Composable +fun GenericComposableBottomSheet( + bottomSheetData: AmcBottomSheetData?, + changeListener: ((ActionData) -> Unit)? = null, + action: ((ActionData, String?) -> Unit)? = null, + onClick: ((actionData: ActionData?) -> Unit)? = null, + onHopperStart: + (( + actionData: CtaData?, + buttonState: MutableState?, + selectedItem: String? + ) -> Unit)? = + null +) { + val viewModel: GenericBottomSheetVM = hiltViewModel() + val selectedItem = remember { mutableStateOf(null) } + + bottomSheetData?.let { + Column(modifier = Modifier.fillMaxWidth()) { + BottomSheetHeader( + it.title, + it.subtitle, + it.leftIcon, + it.centerIcon, + it.rightIcon, + action, + onHopperStart + ) + Box( + modifier = + Modifier.fillMaxWidth().background(color = Color(0xFFF5F5F5)).height(4.dp) + ) {} + BottomSheetContent(it, changeListener, selectedItem, viewModel, onClick, onHopperStart) + Spacer(modifier = Modifier.height(24.dp)) + BottomSheetFooter(it, action, selectedItem, viewModel, onClick, onHopperStart) + } + } +} + +@Composable +fun BottomSheetHeader( + title: TextFieldData?, + subtitle: TextFieldData?, + leftIcon: ImageFieldData?, + centerIcon: ImageFieldData?, + rightIcon: ImageFieldData?, + action: ((ActionData, String?) -> Unit)? = null, + onHopperStart: + (( + actionData: CtaData?, + buttonState: MutableState?, + selectedItem: String? + ) -> Unit)? = + null +) { + Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + Row(horizontalArrangement = Arrangement.Start) { + leftIcon?.let { + NaviImage(imageFieldData = it, modifier = Modifier.width(24.dp).height(24.dp)) + } + title?.let { + NaviTextWidgetized( + textFieldData = it, + modifier = + Modifier.setPadding( + ComposePadding(start = 0, end = 0, top = 0, bottom = 0) + ) + ) + } + centerIcon?.let { + NaviImage( + imageFieldData = it, + modifier = + Modifier.width(90.dp).height(30.dp).padding(top = 5.dp, start = 8.dp) + ) + } + } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + modifier = + Modifier.clickable( + onClick = { + action?.invoke(ActionData(url = "dismiss"), null) + onHopperStart?.invoke( + ActionData(url = "dismiss").toCtaData(), + null, + null + ) + } + ), + horizontalArrangement = Arrangement.End + ) { + rightIcon?.let { + NaviImage( + imageFieldData = it, + modifier = Modifier.width(24.dp).height(24.dp) + ) + } + } + } + } + subtitle?.let { + Row( + modifier = Modifier.fillMaxWidth().padding(top = 8.dp), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + NaviTextWidgetized(textFieldData = it, modifier = Modifier.fillMaxWidth()) + } + } + } +} + +@Composable +fun BottomSheetContent( + bottomSheetData: AmcBottomSheetData?, + changeListener: ((ActionData) -> Unit)?, + selectedItem: MutableState, + viewModel: GenericBottomSheetVM, + onClick: ((actionData: ActionData?) -> Unit)? = null, + onHopperStart: + ((actionData: CtaData?, buttonState: MutableState?, String?) -> Unit)? = + null +) { + Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp)) { + if (bottomSheetData?.itemList?.isNotEmpty() == true) { + Spacer(modifier = Modifier.height(16.dp)) + } + Column { bottomSheetData?.itemList?.forEach { BottomSheetItem(it) } } + Column(modifier = Modifier.fillMaxWidth().padding(top = 8.dp)) { + bottomSheetData?.containerList?.forEach { + BottomSheetContainer(it, changeListener, selectedItem, onClick) + } + } + } +} + +@Composable +fun BottomSheetItem(item: PairItems) { + Row( + modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + item.leftItem?.let { + NaviTextWidgetized(textFieldData = it, modifier = Modifier.width(110.dp)) + } + item.separator?.let { + NaviTextWidgetized(textFieldData = it, modifier = Modifier.padding(start = 8.dp)) + } + item.rightItem?.let { + NaviTextWidgetized( + textFieldData = it, + modifier = Modifier.weight(1f).padding(start = 16.dp) + ) + } + } +} + +@Composable +fun BottomSheetContainer( + containerIem: ContainerIem, + changeListener: ((ActionData) -> Unit)?, + selectedItem: MutableState, + onClick: ((actionData: ActionData?) -> Unit)? = null +) { + val isSelected = + if (containerIem.highlightSelection == false) false + else + (selectedItem.value == containerIem.id || + (selectedItem.value == null && containerIem.preSelected == true)) + if (isSelected) { + selectedItem.value = containerIem.id + } + val bgColor = if (isSelected) Color(0xFF22A940) else Color(0xFFEBEBEB) + val selectedIcon = if (isSelected) containerIem.selectedRightIcon else containerIem.rightIcon + + if (containerIem.isVisible == true) { + containerIem.let { + Row( + modifier = + Modifier.padding(top = 16.dp) + .fillMaxWidth() + .shadow( + elevation = if (isSelected) 16.dp else 0.dp, + spotColor = if (isSelected) Color(0x4DB0C0D9) else DefaultShadowColor, + ambientColor = + if (isSelected) Color(0x4DB0C0D9) else DefaultShadowColor, + ) + .background( + color = Color.White, + shape = RoundedCornerShape(4.dp), + ) + .border( + width = 1.dp, + color = bgColor, + shape = RoundedCornerShape(size = 4.dp) + ) + .clickableWithNoGesture { + if (it.singleSelection && it.isClickable == true) { + selectedItem.value = it.id + } + } + .clip(RoundedCornerShape(size = 4.dp)) + ) { + Column { + Row( + modifier = + Modifier.fillMaxWidth().padding(vertical = 18.dp, horizontal = 16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + it.leftIcon?.let { + NaviImage( + imageFieldData = it, + modifier = Modifier.width(40.dp).height(40.dp) + ) + } + Column { + it.leftTitle?.let { + NaviTextWidgetized( + textFieldData = it, + modifier = Modifier.padding(start = 8.dp) + ) + } + it.leftSubtitle?.let { + NaviTextWidgetized( + textFieldData = it, + modifier = Modifier.padding(start = 8.dp, top = 2.dp) + ) + } + } + Row( + modifier = Modifier.fillMaxWidth().padding(16.dp), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically + ) { + Column { + it.rightTitle?.let { + NaviTextWidgetized( + textFieldData = it, + modifier = + Modifier.align(Alignment.End) + .padding(start = 8.dp) + .clickableWithNoGesture { + containerIem.actionData?.let { action -> + changeListener?.invoke(action) + onClick?.invoke(action) + } + } + ) + } + selectedIcon?.let { + NaviImage( + imageFieldData = it, + modifier = Modifier.width(24.dp).height(24.dp) + ) + } + } + } + } + it.subText?.let { subText -> + Row( + modifier = + Modifier.fillMaxWidth() + .background( + color = + containerIem.subTextBgColor?.let { + Color(it.parseColor()) + } ?: Color.Transparent, + ) + .padding(horizontal = 8.dp, vertical = 2.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + NaviTextWidgetized(textFieldData = subText) + } + } + } + } + } + } +} + +@Composable +fun BottomSheetFooter( + bottomSheetData: AmcBottomSheetData, + action: ((ActionData, String?) -> Unit)?, + selectedItem: MutableState, + viewModel: GenericBottomSheetVM, + onClick: ((actionData: ActionData?) -> Unit)? = null, + onHopperStart: + (( + actionData: CtaData?, + buttonState: MutableState?, + selectedItem: String? + ) -> Unit)? = + null +) { + Row( + modifier = + Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp, bottom = 16.dp, top = 8.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Row { + bottomSheetData.secondaryButtonText?.let { + NaviTextWidgetized(textFieldData = it, modifier = Modifier.padding(12.dp)) + } + } + Row( + modifier = + Modifier.fillMaxWidth() + .clickableWithNoGesture { + bottomSheetData.actionData?.let { + action?.invoke(it, selectedItem.value) + onHopperStart?.invoke(it.toCtaData(), null, selectedItem.value) + } + } + .background(color = Color(0xFF1F002A), shape = RoundedCornerShape(4.dp)), + horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.CenterHorizontally), + verticalAlignment = Alignment.CenterVertically, + ) { + bottomSheetData.buttonText?.let { + NaviTextWidgetized(textFieldData = it, modifier = Modifier.padding(12.dp)) + } + } + } + } +} diff --git a/android/navi-common/src/main/java/com/navi/common/ui/fragment/SingleSelectionBottomSheet.kt b/android/navi-common/src/main/java/com/navi/common/ui/fragment/SingleSelectionBottomSheet.kt index 2af846233e..580f3154cc 100644 --- a/android/navi-common/src/main/java/com/navi/common/ui/fragment/SingleSelectionBottomSheet.kt +++ b/android/navi-common/src/main/java/com/navi/common/ui/fragment/SingleSelectionBottomSheet.kt @@ -1,6 +1,6 @@ /* * - * * Copyright © 2022 by Navi Technologies Limited + * * Copyright © 2022-2024 by Navi Technologies Limited * * All rights reserved. Strictly confidential * */ @@ -9,19 +9,29 @@ package com.navi.common.ui.fragment import android.os.Bundle import android.view.LayoutInflater +import android.view.View import android.view.ViewStub +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import androidx.databinding.DataBindingUtil import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import com.navi.base.model.ActionData import com.navi.common.R import com.navi.common.databinding.SingleSelectionBottomSheetBinding import com.navi.common.databinding.SingleSelectionItemBinding +import com.navi.common.pushnotification.NotificationConstants.DISMISS +import com.navi.design.textview.NaviTextView +import com.navi.design.utils.dpToPxInInt +import com.navi.design.utils.parseColorSafe import com.navi.design.utils.setSpannableString import com.navi.naviwidgets.models.LineItemData import com.navi.naviwidgets.models.SingleSelectionBottomSheetData class SingleSelectionBottomSheet : BaseBottomSheet() { private lateinit var binding: SingleSelectionBottomSheetBinding + private var action: ((ActionData) -> Unit)? = null + private var previouslySelectedItem: SingleSelectionItemBinding? = null private val _selectedItem = MutableLiveData() val selectedItem: LiveData get() = _selectedItem @@ -37,17 +47,91 @@ class SingleSelectionBottomSheet : BaseBottomSheet() { val data = arguments?.getParcelable(DATA) val inflater = LayoutInflater.from(context) binding.titleTv.setSpannableString(data?.title) - data?.items?.forEach { item -> - val view = - inflater.inflate(R.layout.single_selection_item, binding.itemContainer, false) - val itemBinding: SingleSelectionItemBinding? = DataBindingUtil.bind(view) - itemBinding?.titleTv?.setSpannableString(item.title) - itemBinding?.root?.setOnClickListener { - item.position = data.position - _selectedItem.value = item - safelyDismissDialog() + val isRadioType = data?.radioType ?: false + if (isRadioType) { + val selectedRadioDrawable = + ContextCompat.getDrawable(requireContext(), R.drawable.ic_radio_button_selected) + val unSelectedRadioDrawable = + ContextCompat.getDrawable(requireContext(), R.drawable.ic_radio_button_default) + data?.items?.forEach { item -> + val view = + inflater.inflate(R.layout.single_selection_item, binding.itemContainer, false) + val itemBinding: SingleSelectionItemBinding? = DataBindingUtil.bind(view) + itemBinding?.titleTv?.setSpannableString(item.title) + if (item.id == data.defaultSelectedItem?.id) { + itemBinding?.radioBtn?.setImageDrawable(selectedRadioDrawable) + previouslySelectedItem = itemBinding + } else { + itemBinding?.radioBtn?.setImageDrawable(unSelectedRadioDrawable) + } + itemBinding?.root?.setOnClickListener { + previouslySelectedItem?.radioBtn?.setImageDrawable(unSelectedRadioDrawable) + itemBinding.radioBtn.setImageDrawable(selectedRadioDrawable) + previouslySelectedItem = itemBinding + item.position = data.position + data.defaultSelectedItem = item // Confirm if this approach is correct + _selectedItem.value = item + } + binding.itemContainer.addView(view) + } + data?.actionData?.let { + binding.btnContainer.visibility = View.VISIBLE + setFooter(data.horizontalActions, data.actionData) + } + } else { + data?.items?.forEach { item -> + val view = + inflater.inflate(R.layout.single_selection_item, binding.itemContainer, false) + val itemBinding: SingleSelectionItemBinding? = DataBindingUtil.bind(view) + itemBinding?.titleTv?.setSpannableString(item.title) + itemBinding?.root?.setOnClickListener { + item.position = data.position + _selectedItem.value = item + safelyDismissDialog() + } + binding.itemContainer.addView(view) + } + binding.itemContainer.setPadding(0, 0, 0, dpToPxInInt(16)) + } + } + + private fun setFooter(isHorizontalAction: Boolean, actionData: ActionData?) { + binding.apply { + var secondaryActionView: NaviTextView? = null + if (isHorizontalAction) { + secondarybtn.visibility = View.GONE + secondarySideBtn.visibility = View.VISIBLE + secondaryActionView = secondarySideBtn + } else { + secondarybtn.visibility = View.VISIBLE + secondarySideBtn.visibility = View.GONE + secondaryActionView = secondarybtn + } + + primarybtn.apply { + isVisible = actionData?.primaryAction != null + text = actionData?.primaryAction?.title + actionData?.primaryAction?.titleColor?.let { setTextColor(it.parseColorSafe()) } + val primaryActionData = actionData?.primaryAction + setOnClickListener { + primaryActionData?.let { primaryActionData -> + if (primaryActionData.url != DISMISS) { + action?.invoke(primaryActionData) + } + } + safelyDismissDialog() + } + } + secondaryActionView.apply { + isVisible = actionData?.secondaryAction != null + text = actionData?.secondaryAction?.title + actionData?.secondaryAction?.titleColor?.let { setTextColor(it.parseColorSafe()) } + val secondaryActionData = actionData?.secondaryAction + setOnClickListener { + secondaryActionData?.let { action?.invoke(it) } + safelyDismissDialog() + } } - binding.itemContainer.addView(view) } } diff --git a/android/navi-common/src/main/java/com/navi/common/utils/Constants.kt b/android/navi-common/src/main/java/com/navi/common/utils/Constants.kt index 6bf224c576..00b5ce2831 100644 --- a/android/navi-common/src/main/java/com/navi/common/utils/Constants.kt +++ b/android/navi-common/src/main/java/com/navi/common/utils/Constants.kt @@ -285,6 +285,10 @@ object Constants { const val AMC_HPC_PAN_REDIRECT_PAGE_URL = "amc/checker/HPC_PAN_REDIRECTION_PAGE" const val AMC_HPC_NAME_REDIRECT_PAGE_URL = "amc/checker/HPC_NAME_REDIRECTION_PAGE" const val AMC_SIP_MODIFY_URL = "amc/fund/sip_modification" + const val AMC_FUND_OTP = "amc/fund/otp" + const val GET_MULTI_BOTTOMSHEET = "get_multi_bottomsheet" + const val AMC_FUND_AUTOPAY_SETUP_V3 = "amc/fund/auto_pay_setup_v3" + const val AUTOPAY_PAYMENT_CALLBACK_SYNC = "amc/checker/autopay_payment_callback_sync" /* Cta_Data */ const val KEY_CTA_DATA = "CtaData" diff --git a/android/navi-common/src/main/java/com/navi/common/viewmodel/GenericBottomSheetVM.kt b/android/navi-common/src/main/java/com/navi/common/viewmodel/GenericBottomSheetVM.kt new file mode 100644 index 0000000000..e82c4cbfc2 --- /dev/null +++ b/android/navi-common/src/main/java/com/navi/common/viewmodel/GenericBottomSheetVM.kt @@ -0,0 +1,53 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.common.viewmodel + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.navi.base.model.ActionData +import com.navi.common.model.AmcBottomSheetData +import com.navi.common.model.PairItems +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlinx.coroutines.launch + +@HiltViewModel +class GenericBottomSheetVM @Inject constructor() : BaseVM() { + private val _itemList = MutableLiveData>() + val itemList: LiveData> = _itemList + + private val _selectedContainerId = MutableLiveData() + val selectedContainerId: LiveData = _selectedContainerId + + private val _actionData = MutableLiveData() + val actionData: LiveData = _actionData + + private val _bottomSheetData = MutableLiveData() + val bottomSheetData: LiveData = _bottomSheetData + + fun setItemList(dates: List) { + _itemList.value = dates + } + + fun setSelectedContainerId(mode: String?) { + mode?.let { _selectedContainerId.value = mode } + } + + fun updateItemList(newDates: List) { + viewModelScope.launch { _itemList.value = newDates } + } + + fun performAction(action: ActionData?) { + _actionData.value = action + } + + fun setBottomSheetData(data: AmcBottomSheetData?) { + _bottomSheetData.value = data + } +} diff --git a/android/navi-amc/src/main/res/drawable/ic_radio_button_default.xml b/android/navi-common/src/main/res/drawable/ic_radio_button_default.xml similarity index 100% rename from android/navi-amc/src/main/res/drawable/ic_radio_button_default.xml rename to android/navi-common/src/main/res/drawable/ic_radio_button_default.xml diff --git a/android/navi-amc/src/main/res/drawable/ic_radio_button_selected.xml b/android/navi-common/src/main/res/drawable/ic_radio_button_selected.xml similarity index 100% rename from android/navi-amc/src/main/res/drawable/ic_radio_button_selected.xml rename to android/navi-common/src/main/res/drawable/ic_radio_button_selected.xml diff --git a/android/navi-common/src/main/res/layout/single_selection_bottom_sheet.xml b/android/navi-common/src/main/res/layout/single_selection_bottom_sheet.xml index 6f96a861bb..191795463f 100644 --- a/android/navi-common/src/main/res/layout/single_selection_bottom_sheet.xml +++ b/android/navi-common/src/main/res/layout/single_selection_bottom_sheet.xml @@ -7,7 +7,8 @@ ~ --> - + + + + + + + + + + + \ No newline at end of file diff --git a/android/navi-common/src/main/res/layout/single_selection_item.xml b/android/navi-common/src/main/res/layout/single_selection_item.xml index 2f82c94999..998e94f0c8 100644 --- a/android/navi-common/src/main/res/layout/single_selection_item.xml +++ b/android/navi-common/src/main/res/layout/single_selection_item.xml @@ -7,7 +7,7 @@ ~ --> - + + \ No newline at end of file diff --git a/android/navi-gold/src/main/java/com/navi/gold/ui/DigitalGoldHomeActivity.kt b/android/navi-gold/src/main/java/com/navi/gold/ui/DigitalGoldHomeActivity.kt index 805fe2a456..8e5512cd83 100644 --- a/android/navi-gold/src/main/java/com/navi/gold/ui/DigitalGoldHomeActivity.kt +++ b/android/navi-gold/src/main/java/com/navi/gold/ui/DigitalGoldHomeActivity.kt @@ -1166,10 +1166,13 @@ class DigitalGoldHomeActivity : ) { goldContainerWidget = item goldContainerWidget?.widgetData?.pricePerMg = it - goldContainerWidget?.widgetData?.marketPricePerMg = - response.extraData?.marketPricePerMg - goldContainerWidget?.widgetData?.naviPricePerMg = - response.extraData?.naviPricePerMg + response.extraData?.marketPricePerMg?.let { marketPricePerMg -> + goldContainerWidget?.widgetData?.marketPricePerMg = + marketPricePerMg + } + response.extraData?.naviPricePerMg?.let { naviPricePerMg -> + goldContainerWidget?.widgetData?.naviPricePerMg = naviPricePerMg + } } } naviAdapter.notifyItemChanged( diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/base/InputWidgetData.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/base/InputWidgetData.kt index 2999fc91b2..ac897793c3 100644 --- a/android/navi-widgets/src/main/java/com/navi/naviwidgets/base/InputWidgetData.kt +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/base/InputWidgetData.kt @@ -27,6 +27,7 @@ abstract class InputWidgetData( @SerializedName("isPurpleTheme") var isPurpleTheme: Boolean? = null, @SerializedName("isNonEditable") var isNonEditable: Boolean? = null, @SerializedName("iconCode") var iconCode: String? = null, + @SerializedName("leftIconCode", alternate = ["leftIcon"]) var leftIconCode: String? = null, @SerializedName("infoCta") val infoCta: InfoCta? = null, @SerializedName("validation") var inputValidationList: List? = null, @SerializedName("validateOnTextChange") val validateOnTextChange: Boolean? = false, diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/models/LineItemData.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/models/LineItemData.kt index 2d6ac8ae95..7580cae114 100644 --- a/android/navi-widgets/src/main/java/com/navi/naviwidgets/models/LineItemData.kt +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/models/LineItemData.kt @@ -1,6 +1,6 @@ /* * - * * Copyright © 2019-2023 by Navi Technologies Limited + * * Copyright © 2019-2024 by Navi Technologies Limited * * All rights reserved. Strictly confidential * */ @@ -17,7 +17,8 @@ import kotlinx.parcelize.Parcelize data class LineItemData( @SerializedName("title") val title: TextWithStyle?, @SerializedName("id") val id: String?, - @SerializedName("actionData") val actionData: ActionData? + @SerializedName("actionData") val actionData: ActionData?, + @SerializedName("secondaryId") val secondaryId: String? ) : Parcelable { var position: Int? = null } diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/models/SingleSelectionBottomSheetData.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/models/SingleSelectionBottomSheetData.kt index 3a597e1ca6..c7615798ea 100644 --- a/android/navi-widgets/src/main/java/com/navi/naviwidgets/models/SingleSelectionBottomSheetData.kt +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/models/SingleSelectionBottomSheetData.kt @@ -1,6 +1,6 @@ /* * - * * Copyright © 2019-2023 by Navi Technologies Limited + * * Copyright © 2019-2024 by Navi Technologies Limited * * All rights reserved. Strictly confidential * */ @@ -9,13 +9,18 @@ package com.navi.naviwidgets.models import android.os.Parcelable import com.google.gson.annotations.SerializedName +import com.navi.base.model.ActionData import com.navi.design.textview.model.TextWithStyle import kotlinx.parcelize.Parcelize @Parcelize data class SingleSelectionBottomSheetData( @SerializedName("title") val title: TextWithStyle?, - @SerializedName("items", alternate = ["data"]) val items: List? + @SerializedName("items", alternate = ["data"]) val items: List?, + @SerializedName("action", alternate = ["actionData"]) val actionData: ActionData? = null, + @SerializedName("horizontalActions") val horizontalActions: Boolean = false, + @SerializedName("defaultSelectedItem") var defaultSelectedItem: LineItemData? = null, + @SerializedName("radioType") val radioType: Boolean? = false ) : Parcelable { var position: Int? = null } diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/utils/Utils.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/utils/Utils.kt index bade27a30c..eef6c68f93 100644 --- a/android/navi-widgets/src/main/java/com/navi/naviwidgets/utils/Utils.kt +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/utils/Utils.kt @@ -1080,6 +1080,27 @@ fun setButtonLoaderState( } } +fun setButtonLoaderStateNonShrinking( + button: NaviTextView, + buttonLoader: LottieAnimationView, + isLoading: Boolean, + text: String, + initialTextColor: Int? = null +) { + if (isLoading) { + button.setTextColor(Color.TRANSPARENT) + buttonLoader.visibility = View.VISIBLE + buttonLoader.playAnimation() + button.isEnabled = false + } else { + button.setTextColor(initialTextColor ?: Color.WHITE) + button.text = text + buttonLoader.visibility = View.GONE + buttonLoader.pauseAnimation() + button.isEnabled = true + } +} + inline fun startCoroutineTimer( delayMillis: Long = 0, repeatMillis: Long = 0, diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/widgets/labeledinputsearch/InputSearchWidgetMeta.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/widgets/labeledinputsearch/InputSearchWidgetMeta.kt index 92d892ce83..2892f4fa8a 100644 --- a/android/navi-widgets/src/main/java/com/navi/naviwidgets/widgets/labeledinputsearch/InputSearchWidgetMeta.kt +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/widgets/labeledinputsearch/InputSearchWidgetMeta.kt @@ -1,6 +1,6 @@ /* * - * * Copyright © 2023 by Navi Technologies Limited + * * Copyright © 2023-2024 by Navi Technologies Limited * * All rights reserved. Strictly confidential * */ @@ -10,6 +10,7 @@ package com.navi.naviwidgets.widgets.labeledinputsearch import android.os.Parcelable import com.google.gson.annotations.SerializedName import com.navi.base.model.ActionData +import com.navi.naviwidgets.models.LineItemData import kotlinx.parcelize.Parcelize @Parcelize @@ -25,7 +26,9 @@ data class InputSearchWidgetMeta( @SerializedName("searchResultsHeading") var searchResultsHeading: String? = null, @SerializedName("enableOnlyClick") var enableOnlyClick: Boolean? = null, @SerializedName("actionData") var actionData: ActionData? = null, - @SerializedName("showCrossIcon") var showCrossIcon: Boolean? = null + @SerializedName("showCrossIcon") var showCrossIcon: Boolean? = null, + @SerializedName("savedTextVariations") var savedTextVariations: List? = null, + @SerializedName("defaultSelectedItem") var defaultSelectedItem: LineItemData? = null ) : Parcelable @Parcelize diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/widgets/labeledinputsearch/ui/LabeledTextSearchInputWidget.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/widgets/labeledinputsearch/ui/LabeledTextSearchInputWidget.kt index 5d6722fb16..37f173010e 100644 --- a/android/navi-widgets/src/main/java/com/navi/naviwidgets/widgets/labeledinputsearch/ui/LabeledTextSearchInputWidget.kt +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/widgets/labeledinputsearch/ui/LabeledTextSearchInputWidget.kt @@ -27,6 +27,7 @@ import com.navi.naviwidgets.base.InputWidgetModel import com.navi.naviwidgets.callbacks.WidgetCallback import com.navi.naviwidgets.databinding.ViewLabeledTextInputBinding import com.navi.naviwidgets.extensions.setMaxCharLength +import com.navi.naviwidgets.extensions.showWhenDataIsAvailable import com.navi.naviwidgets.utils.EMPTY import com.navi.naviwidgets.utils.NaviWidgetIconUtils import com.navi.naviwidgets.validations.BaseInputValidation @@ -44,6 +45,8 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 private var widgetCallback: WidgetCallback? = null var shouldShowCrossIcon: Boolean = false + private var showRightIcon: Boolean = false + private var defaultShowCrossIcon: Boolean = true fun updateData( inputWidgetModel: InputWidgetModel, @@ -123,7 +126,9 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 widgetModel.inputData = it widgetBinding.plainTextInput.setText(it) } - widgetBinding.crossIv.isVisible = true + if (defaultShowCrossIcon) { + widgetBinding.crossIv.isVisible = true + } } ?: run { widgetModel.inputData = null @@ -146,7 +151,9 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 private fun updateSearchIcon(iconCode: String?) { if (iconCode.isNullOrEmpty().not()) { val icon = NaviWidgetIconUtils.getIconResourceId(iconCode.orEmpty()) - if (widgetBinding.plainTextInput.text.isNullOrEmpty() && icon != -1) { + if ( + (widgetBinding.plainTextInput.text.isNullOrEmpty() || showRightIcon) && icon != -1 + ) { widgetBinding.plainTextInput.setCompoundDrawablesWithIntrinsicBounds(0, 0, icon, 0) } else { widgetBinding.plainTextInput.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0) @@ -154,6 +161,17 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 } } + fun setDefaultShowCrossIcon(toShowDefaultCrossIcon: Boolean) { + defaultShowCrossIcon = toShowDefaultCrossIcon + } + + fun setSavedTextVariations(id: String?) { + widgetModel.widgetData?.inputTextSearchItemData?.savedTextVariations?.let { + val savedText = it.firstOrNull { it.id == id }?.title?.text + setText(savedText.orEmpty()) + } + } + fun setText(text: String, validateInput: Boolean = true) { widgetBinding.plainTextInput.setText(text) widgetModel.inputData = text @@ -164,6 +182,14 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 } } + fun setLeftIcon(iconCode: String?) { + widgetBinding.startIcon.showWhenDataIsAvailable(iconCode) + } + + fun toggleRightIconState(toShowRightIcon: Boolean) { + showRightIcon = toShowRightIcon + } + override fun initiateUI() { binding.infoTv.isVisible = false widgetBinding = @@ -212,6 +238,13 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 } widgetBinding.plainTextInput.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0) } + + fun setDateId(sipType: String?): String? { + return widgetModel.widgetData?.inputTextSearchItemData?.savedTextVariations?.let { + val dateId = it.firstOrNull { it.id == sipType }?.secondaryId + dateId + } + } } data class FilterAction(val text: String) : NaviClickAction()