TP-49159 | PS | ELSS/US fund warning bsheet (#9374)

This commit is contained in:
Prakhar Saxena
2024-01-18 19:49:54 +05:30
committed by GitHub
parent b12026d75e
commit 33082d4e9f
9 changed files with 245 additions and 842 deletions

View File

@@ -8,6 +8,7 @@
package com.navi.amc.common.fragment
import android.os.Bundle
import android.view.View
import android.view.ViewStub
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
@@ -21,6 +22,9 @@ import com.navi.amc.utils.SubPageStatusType
import com.navi.base.model.ActionData
import com.navi.common.listeners.NewBottomSheetListener
import com.navi.common.ui.fragment.BaseBottomSheet
import com.navi.design.textview.NaviTextView
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.extensions.showWhenDataIsAvailable
@@ -38,34 +42,12 @@ class AmcCommonBottomSheet : BaseBottomSheet() {
icon.showWhenDataIsAvailable(response.iconCode)
title.setSpannableString(response.title)
subtitle.setSpannableString(response.subTitle, ::onSpanClick)
primarybtn.apply {
isVisible = response.actionData?.primaryAction != null
text = response.actionData?.primaryAction?.title
response.actionData?.primaryAction?.titleColor?.let {
setTextColor(it.parseColorSafe())
}
val actionData = response.actionData?.primaryAction
setOnClickListener {
if (actionData != null) {
action?.invoke(actionData)
}
safelyDismissDialog()
}
}
secondarybtn.apply {
isVisible = response.actionData?.secondaryAction != null
text = response.actionData?.secondaryAction?.title
response.actionData?.secondaryAction?.titleColor?.let {
setTextColor(it.parseColorSafe())
}
val actionData = response.actionData?.secondaryAction
setOnClickListener {
if (actionData != null) {
action?.invoke(actionData)
}
safelyDismissDialog()
}
}
noteBanner.background = getNaviDrawable(
cornerRadius = dpToPxInInt(4),
backgroundColor = response.note?.bgColor.parseColorSafe()
)
noteBanner.setSpannableString(response.note?.title)
setFooter(response.horizontalActions, response.actionData)
AmcAnalytics.sendEvent(eventsData = response.metaData?.viewedData, screenName = screenName)
}
}
@@ -75,6 +57,50 @@ class AmcCommonBottomSheet : BaseBottomSheet() {
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 {
action?.invoke(it)
}
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()
}
}
}
}
override val screenName: String
get() = SubPageStatusType.AMC_COMMON_BOTTOMSHEET

View File

@@ -26,4 +26,17 @@ data class AmcCommonBottomSheetData(
val actionData: ActionData? = null,
@SerializedName("metaData")
val metaData: GenericAnalytics? = null,
@SerializedName("horizontalActions")
val horizontalActions: Boolean = false,
@SerializedName("note")
val note: AmcCommonBsheetNoteData? = null,
): Parcelable
@Parcelize
data class AmcCommonBsheetNoteData(
@SerializedName("title")
val title: TextWithStyle? = null,
@SerializedName("bgColor")
val bgColor: String? = null
): Parcelable

View File

@@ -1,588 +0,0 @@
/*
*
* * Copyright © 2022-2023 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.View
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.viewModels
import com.navi.amc.R
import com.navi.amc.common.fragment.AmcBaseFragment
import com.navi.amc.common.taskProcessor.AmcTaskManager
import com.navi.amc.databinding.FundBuyLayoutBinding
import com.navi.amc.fundbuy.models.SipDetailsData
import com.navi.amc.fundbuy.viewmodel.FundBuyViewModel
import com.navi.amc.utils.*
import com.navi.amc.utils.AmcAnalytics.AMC_BTN_INVEST_SETUP_CREATE_LUMPSUM
import com.navi.amc.utils.AmcAnalytics.AMC_BTN_INVEST_SETUP_CREATE_SIP
import com.navi.amc.utils.AmcAnalytics.FUND_BUY_SIP_LUMPSUM_SCREEN
import com.navi.amc.utils.AmcAnalytics.FUND_ID
import com.navi.amc.utils.AmcAnalytics.ISIN
import com.navi.amc.utils.AmcAnalytics.PILL
import com.navi.amc.utils.AmcAnalytics.SIP_DATE
import com.navi.amc.utils.AmcAnalytics.SIP_FREQUENCY
import com.navi.amc.utils.AmcAnalytics.TRANS_TYPE
import com.navi.amc.utils.Constant.AMOUNT
import com.navi.amc.utils.Constant.AMOUNT_SOURCE
import com.navi.amc.utils.Constant.CALENDAR_DATA
import com.navi.amc.utils.Constant.DATA
import com.navi.amc.utils.Constant.FUND_HEADER
import com.navi.amc.utils.Constant.LUMPSUM
import com.navi.amc.utils.Constant.MONTHLY
import com.navi.amc.utils.Constant.SIP
import com.navi.amc.utils.Constant.SIP_REFERENCE_ID
import com.navi.amc.utils.Constant.TYPE
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.isNotNullAndNotEmpty
import com.navi.base.utils.orFalse
import com.navi.common.listeners.FragmentInterchangeListener
import com.navi.common.listeners.HeaderInteractionListener
import com.navi.common.ui.fragment.SingleSelectionBottomSheet
import com.navi.common.utils.SPACE
import com.navi.common.utils.formatDateIntoPrefixMonthNamesWithYear
import com.navi.common.utils.getDateWithoutSuffix
import com.navi.common.utils.observeNonNull
import com.navi.design.utils.*
import com.navi.naviwidgets.base.InputWidgetModel
import com.navi.naviwidgets.callbacks.WidgetCallback
import com.navi.naviwidgets.extensions.showWhenDataIsAvailable
import com.navi.naviwidgets.models.SingleSelectionBottomSheetData
import com.navi.naviwidgets.utils.getGradientDrawable
import com.navi.naviwidgets.widgets.fixedhinttextinput.LabeledTextInputFixedHintWidgetModel
import com.navi.naviwidgets.widgets.textwithsearch.TextSearchClickAction
import dagger.hilt.android.AndroidEntryPoint
import java.util.*
@AndroidEntryPoint
class FundBuyingFragment : AmcBaseFragment(), WidgetCallback {
private lateinit var binding: FundBuyLayoutBinding
private val viewModel by viewModels<FundBuyViewModel>()
override val screenName: String
get() = FUND_BUY_SIP_LUMPSUM_SCREEN
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
fetchScreenData()
}
override fun setContainerView(viewStub: ViewStub) {
viewStub.layoutResource = R.layout.fund_buy_layout
binding = DataBindingUtil.getBinding(viewStub.inflate())!!
initError(viewModel)
initObservers()
}
private fun fetchScreenData() {
showLoader()
val isin = arguments?.getString(ISIN)
isin?.let { viewModel.getFundBuyScreenData(it) }
}
private fun initUI(checked: Boolean) {
binding.switchView.setProperties(SIP, LUMPSUM, checked) {
viewModel.isSipSelected = it
if (it) {
binding.sipFrequency.visibility = View.VISIBLE
if (viewModel.sipType.isNullOrEmpty().not()) {
if (!viewModel.toHideDate) binding.date.visibility = View.VISIBLE
binding.sipAmount.visibility = View.VISIBLE
binding.sipRecommended.isVisible = true
}
if (viewModel.isAutoPayThere())
binding.autopayCheckbox.root.visibility = View.VISIBLE
binding.lumpsumAmount.visibility = View.GONE
binding.lumpsumRecommended.isVisible = false
if (!viewModel.isSipTutorialCrossed)
binding.sipTutorial.root.visibility = View.VISIBLE
binding.lumpsumTutorial.root.visibility = View.GONE
binding.footer.btn.text = resources.getString(R.string.create_sip)
} else {
if (!viewModel.isLumpSumTutorialCrossed)
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.footer.btn.text = resources.getString(R.string.invest_now)
}
}
}
private fun initObservers() {
viewModel.fundBuyScreenData.observe(viewLifecycleOwner) {
hideLoader()
it?.header?.let { setHeaderProperties(it) }
it?.content?.let {
binding.header.setProperties(it.fundHeaderData, ::navigate)
viewModel.isSipSelected = it.fundInvestmentType?.defaultChecked.equals(SIP)
val sipFrequencyData = it.fundInvestmentType?.sipData?.getOrNull(0)
binding.sipFrequency.updateData(sipFrequencyData as InputWidgetModel, this, 0)
val amount = it.fundInvestmentType?.sipData?.getOrNull(1)
binding.sipAmount.updateData(amount as InputWidgetModel, this)
binding.sipAmount.widgetBinding.plainTextInput.id = R.id.sip_amount
val date = it.fundInvestmentType?.sipData?.getOrNull(2)
binding.date.updateData(date as InputWidgetModel, 2, this)
binding.date.widgetBinding.plainTextInput.id = R.id.date
val lumpSum = it.fundInvestmentType.lumpsumData?.getOrNull(0)
binding.lumpsumAmount.updateData(lumpSum as InputWidgetModel, this)
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 { it -> binding.lumpsumRecommended.setSelectedAmount(it) }
}
override fun afterTextChanged(p0: Editable?) {
}
})
it.fundInvestmentType.lumpsumRecommendedAmount?.let {
binding.lumpsumRecommended.setProperties(it) { lsRecommended, action ->
val updatedAmount = viewModel.onChipSelected(binding.lumpsumAmount.getUserInput(), lsRecommended, action)
binding.lumpsumAmount.updateText(
updatedAmount
)
val eventName = if(action?.url.equals(Constant.ADD_AMOUNT)) AmcAnalytics.AMC_BTN_INVEST_SETUP_AMOUNT_INCREMENT_PILLS
else AmcAnalytics.AMC_BTN_INVEST_SETUP_AMOUNT_RECO
sendEvent(
eventName,
hashMapOf(
TRANS_TYPE to LUMPSUM,
AMOUNT to binding.lumpsumAmount.getUserInput().orEmpty(),
FUND_ID to arguments?.getString(ISIN).orEmpty()
)
)
viewModel.lastLumpsumInteraction = PILL
}
viewModel.toShowLumpsumRecommended = true
}
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 { it -> binding.sipRecommended.setSelectedAmount(it) }
}
override fun afterTextChanged(p0: Editable?) {
}
})
it.fundInvestmentType.sipRecommendedAmount?.let {
binding.sipRecommended.setProperties(it) { sipRecommended, action ->
val updatedAmount = viewModel.onChipSelected(binding.sipAmount.getUserInput(), sipRecommended, action)
binding.sipAmount.updateText(
updatedAmount
)
val eventName = if(action?.url.equals(Constant.ADD_AMOUNT)) AmcAnalytics.AMC_BTN_INVEST_SETUP_AMOUNT_INCREMENT_PILLS
else AmcAnalytics.AMC_BTN_INVEST_SETUP_AMOUNT_RECO
sendEvent(
eventName,
hashMapOf(
TRANS_TYPE to SIP,
AMOUNT to binding.sipAmount.getUserInput().orEmpty(),
FUND_ID to arguments?.getString(ISIN).orEmpty()
)
)
viewModel.lastSipInteraction = PILL
}
viewModel.toShowSipRecommended = true
}
binding.terms.setSpannableString(it.terms) {
val data =
if (viewModel.isSipSelected.orFalse())
it.parameters?.singleOrNull { it.key == SIP }?.value
else it.parameters?.singleOrNull { it.key == LUMPSUM }?.value
val bundle = Bundle().apply { putString(DATA, data) }
val bottomSheet = CutOffTimeBankBottomSheet.newInstance(bundle)
safelyShowBottomSheet(bottomSheet, CutOffTimeBankBottomSheet.SCREEN)
}
binding.sipTutorial.root.isVisible =
it.fundInvestmentType.sipTutorial?.let {
binding.sipTutorial.title.setSpannableString(it.title)
binding.sipTutorial.subtitle.setSpannableString(it.subtitle)
binding.sipTutorial.cross.setOnClickListener {
viewModel.isSipTutorialCrossed = true
binding.sipTutorial.root.isVisible = false
}
true
}
?: run { false }
it.fundInvestmentType.lumpsumTutorial?.let {
binding.lumpsumTutorial.root.background =
context?.let { it ->
ContextCompat.getDrawable(it, R.drawable.lumpsum_background)
}
binding.lumpsumTutorial.title.setSpannableString(it.title)
binding.lumpsumTutorial.subtitle.setSpannableString(it.subtitle)
binding.lumpsumTutorial.cross.setOnClickListener {
viewModel.isLumpSumTutorialCrossed = true
binding.lumpsumTutorial.root.isVisible = false
}
}
it.fundInvestmentType.autoPay?.let {
binding.autopayCheckbox.root.isVisible = true
binding.autopayCheckbox.checkbox.isChecked = it.isChecked.orFalse()
binding.autopayCheckbox.title.setSpannableString(it.title)
binding.autopayCheckbox.root.setOnClickListener {
binding.autopayCheckbox.checkbox.isChecked =
binding.autopayCheckbox.checkbox.isChecked.not()
}
}
binding.rewards.root.isVisible = it.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)
}
binding.rewards.label.setSpannableString(rewards.label?.title)
binding.rewards.label.background = getNaviDrawable(
radii = CornerRadius(
leftBottom = dpToPx(4)
), backgroundColor = rewards.label?.bgColor.parseColorSafe()
)
it.rewards.gradient?.let {
binding.rewards.root.background = getGradientDrawable(context, it)
} ?: run {
binding.rewards.root.setBackgroundColor(it.rewards.bgColor.parseColorSafe())
}
true
} ?: run {
false
}
}
it?.footer?.let {
binding.footer.root.visibility = View.VISIBLE
binding.footer.btn.apply {
text = it.nextCta?.title
setTextColor(it.nextCta?.titleColor.parseColorSafe())
setOnClickListener { view -> validation() }
}
}
initUI(it?.content?.fundInvestmentType?.defaultChecked.equals(SIP))
}
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(ISIN, arguments?.getString(ISIN))
putString(TYPE, viewModel.getSipDisplayType())
putString(SIP_REFERENCE_ID, it.sipReferenceId)
}
fragmentInterchangeListener?.navigateToNextScreen(
viewModel.fundBuyScreenData.value?.footer?.nextCta,
bundle
)
}
}
}
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 == Constant.ADD_AMOUNT) {
handleAddAmountWidgetClicked(naviClickAction)
}
else {
val data = naviClickAction.actionData?.parameters?.toMutableList()
if (viewModel.selectedDate.isNotNullAndNotEmpty()) {
data?.add(LineItem(key = Constant.SELECTED_DATE, value = viewModel.selectedDate))
}
val bundle =
Bundle().apply {
putParcelable(CALENDAR_DATA, viewModel.getCalendarData())
putParcelableArrayList(DATA, data as ArrayList<LineItem>)
}
safelyShowDialogFragment(
SipCalendarFragment.newInstance(bundle = bundle) {
viewModel.selectedDate = dateFormat(it)
binding.date.apply {
val calendar = Calendar.getInstance()
calendar.time = it
setText(formatDateIntoPrefixMonthNamesWithYear(it))
setSuccessText(
getDateWithoutSuffix(calendar) +
SPACE +
resources.getString(R.string.of_every_month)
)
}
},
SipCalendarFragment.TAG
)
}
}
}
}
private fun openBottomSheet(data: SingleSelectionBottomSheetData?) {
val bottomSheet = SingleSelectionBottomSheet.getInstance(data)
safelyShowBottomSheet(bottomSheet, SingleSelectionBottomSheet.SINGLE_SELECTION_BOTTOM_SHEET)
bottomSheet.selectedItem.observeNonNull(this) { item ->
binding.sipAmount.visibility = View.VISIBLE
viewModel.sipType = item.id
binding.sipRecommended.isVisible = viewModel.toShowSipRecommended
if (item.id == MONTHLY) {
viewModel.toHideDate = false
binding.date.visibility = View.VISIBLE
} else {
viewModel.toHideDate = true
binding.date.visibility = View.GONE
}
binding.sipFrequency.setText(item)
}
}
private fun validation() {
if (viewModel.isSipSelected.orFalse()) {
if (viewModel.toHideDate) {
val frequency = binding.sipFrequency.getUserInputPostValidation()
val amount = binding.sipAmount.getUserInputPostValidation()
if (frequency != null && amount != null) {
sendEvent(
AMC_BTN_INVEST_SETUP_CREATE_SIP,
hashMapOf(
Pair(AmcAnalytics.IS_VALID, AmcAnalytics.TRUE),
Pair(FUND_ID, arguments?.getString(ISIN).orEmpty()),
Pair(SIP_FREQUENCY, frequency.orEmpty()),
Pair(AMOUNT, amount.orEmpty()),
Pair(AMOUNT_SOURCE,viewModel.lastSipInteraction)
),
isNeededForFirebase = true
)
val bottomSheetData =
viewModel.getSipBottomSheetData(
CurrencyUtils.getNormalizedAmount(amount.toBigDecimal())
.replace("", ""),
binding.sipFrequency.getUserInputPostValidation().orEmpty()
)
val bundle = Bundle().apply { putString(DATA, bottomSheetData) }
getBottomSheet(
SubPageStatusType.GENERIC_BOTTOMSHEET,
bundle = bundle,
action = sipWeeklyFortnightlyAction()
)
?.let { safelyShowBottomSheet(it, SubPageStatusType.GENERIC_BOTTOMSHEET) }
} else {
sendEvent(
AMC_BTN_INVEST_SETUP_CREATE_SIP,
hashMapOf(
Pair(AmcAnalytics.IS_VALID, AmcAnalytics.FALSE),
Pair(FUND_ID, arguments?.getString(ISIN).orEmpty()),
Pair(SIP_FREQUENCY, frequency.orEmpty()),
Pair(AMOUNT, amount.orEmpty()),
Pair(AMOUNT_SOURCE, viewModel.lastSipInteraction)
),
isNeededForFirebase = true
)
}
} else {
val frequency = binding.sipFrequency.getUserInputPostValidation()
val amount = binding.sipAmount.getUserInputPostValidation()
val date = binding.date.getUserInputPostValidation()
if (frequency != null && amount != null && date != null) {
sendEvent(
AMC_BTN_INVEST_SETUP_CREATE_SIP,
hashMapOf(
Pair(AmcAnalytics.IS_VALID, AmcAnalytics.TRUE),
Pair(FUND_ID, arguments?.getString(ISIN).orEmpty()),
Pair(SIP_FREQUENCY, frequency.orEmpty()),
Pair(SIP_DATE, date.orEmpty()),
Pair(AMOUNT, amount.orEmpty()),
Pair(AMOUNT_SOURCE, viewModel.lastSipInteraction)
),
isNeededForFirebase = true
)
val bottomSheetData =
viewModel.getSipBottomSheetData(
CurrencyUtils.getNormalizedAmount(amount.toBigDecimal())
.replace("", ""),
binding.sipFrequency.getUserInputPostValidation().orEmpty()
)
val bundle = Bundle().apply { putString(DATA, bottomSheetData) }
getBottomSheet(
SubPageStatusType.GENERIC_BOTTOMSHEET,
bundle = bundle,
action = sipMonthlyAction()
)
?.let { safelyShowBottomSheet(it, SubPageStatusType.GENERIC_BOTTOMSHEET) }
} else {
sendEvent(
AMC_BTN_INVEST_SETUP_CREATE_SIP,
hashMapOf(
Pair(AmcAnalytics.IS_VALID, AmcAnalytics.FALSE),
Pair(FUND_ID, arguments?.getString(ISIN).orEmpty()),
Pair(SIP_FREQUENCY, frequency.orEmpty()),
Pair(SIP_DATE, date.orEmpty()),
Pair(AMOUNT, amount.orEmpty()),
Pair(AMOUNT_SOURCE, viewModel.lastSipInteraction)
),
isNeededForFirebase = true
)
}
}
} else {
if (binding.lumpsumAmount.getUserInputPostValidation() != null) {
sendEvent(
AMC_BTN_INVEST_SETUP_CREATE_LUMPSUM,
hashMapOf(
Pair(AmcAnalytics.IS_VALID, AmcAnalytics.TRUE),
Pair(AMOUNT, binding.lumpsumAmount.getUserInput().toString()),
Pair(FUND_ID, arguments?.getString(ISIN).orEmpty()),
Pair(AMOUNT_SOURCE, viewModel.lastLumpsumInteraction)
),
isNeededForFirebase = true
)
val bundle =
Bundle().apply {
putParcelable(
FUND_HEADER,
viewModel.fundBuyScreenData.value?.content?.fundHeaderData
)
putString(AMOUNT, binding.lumpsumAmount.getUserInput())
putString(ISIN, arguments?.getString(ISIN))
putString(TYPE, LUMPSUM)
}
fragmentInterchangeListener?.navigateToNextScreen(
viewModel.fundBuyScreenData.value?.footer?.nextCta,
bundle
)
} else {
sendEvent(
AMC_BTN_INVEST_SETUP_CREATE_LUMPSUM,
hashMapOf(
Pair(AmcAnalytics.IS_VALID, AmcAnalytics.FALSE),
Pair(FUND_ID, arguments?.getString(ISIN).orEmpty()),
Pair(AMOUNT, binding.lumpsumAmount.getUserInput().toString()),
Pair(AMOUNT_SOURCE, viewModel.lastLumpsumInteraction)
),
isNeededForFirebase = true
)
}
}
}
private fun sipWeeklyFortnightlyAction() =
View.OnClickListener {
val sipDetails =
SipDetailsData(
scheme = arguments?.getString(ISIN),
amount = binding.sipAmount.getUserInputPostValidation(),
frequency = viewModel.sipType,
autoPayChecked =
if (viewModel.isAutoPayThere()) binding.autopayCheckbox.checkbox.isChecked
else null
)
viewModel.postSipDetails(sipDetails)
}
private fun sipMonthlyAction() =
View.OnClickListener {
val sipDetails =
SipDetailsData(
scheme = arguments?.getString(ISIN),
amount = binding.sipAmount.getUserInputPostValidation(),
frequency = viewModel.sipType,
sipDate = viewModel.selectedDate,
autoPayChecked =
if (viewModel.isAutoPayThere()) binding.autopayCheckbox.checkbox.isChecked
else null
)
viewModel.postSipDetails(sipDetails)
}
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)
}
companion object {
fun newInstance(bundle: Bundle): FundBuyingFragment {
return FundBuyingFragment().apply { arguments = bundle }
}
}
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(
TRANS_TYPE to LUMPSUM,
AMOUNT to binding.lumpsumAmount.getUserInput().orEmpty(),
FUND_ID to arguments?.getString(ISIN)
.orEmpty()
)
)
}
}

View File

@@ -29,6 +29,7 @@ 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.DISMISS
import com.navi.amc.utils.Constant.FREQUENCY
import com.navi.amc.utils.Constant.INVESTMENT_TYPE
import com.navi.amc.utils.Constant.SIP_DATE
@@ -791,9 +792,9 @@ class FundBuyingFragmentV2 : AmcBaseFragment(), WidgetCallback {
)
val bundle = Bundle().apply { putString(Constant.DATA, bottomSheetData) }
getBottomSheet(
SubPageStatusType.HORIZONTAL_BUTTON_BOTTOMSHEET,
SubPageStatusType.AMC_COMMON_BOTTOMSHEET,
bundle = bundle,
action = sipWeeklyFortnightlyAction()
genericListener = ::sipWeeklyFortnightlyAction
)?.let { safelyShowBottomSheet(it, SubPageStatusType.HORIZONTAL_BUTTON_BOTTOMSHEET) }
} else {
sendEvent(
@@ -850,9 +851,9 @@ class FundBuyingFragmentV2 : AmcBaseFragment(), WidgetCallback {
)
val bundle = Bundle().apply { putString(Constant.DATA, bottomSheetData) }
getBottomSheet(
SubPageStatusType.HORIZONTAL_BUTTON_BOTTOMSHEET,
SubPageStatusType.AMC_COMMON_BOTTOMSHEET,
bundle = bundle,
action = sipMonthlyAction()
genericListener = ::sipMonthlyAction
)?.let { safelyShowBottomSheet(it, SubPageStatusType.HORIZONTAL_BUTTON_BOTTOMSHEET) }
} else {
sendEvent(
@@ -898,32 +899,17 @@ class FundBuyingFragmentV2 : AmcBaseFragment(), WidgetCallback {
)
)
val ctaUrl = viewModel.getSelectedFooter()?.nextCta?.url.orEmpty()
val bundle = createLumpSumPaymentBundle()
if (ctaUrl.endsWith(SubPageStatusType.OTP, true)) {
bundle.putString(Constant.PAYMENT_MODE, Constant.UPI)
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
)
)
} else {
fragmentInterchangeListener?.navigateToNextScreen(
viewModel.getSelectedFooter()?.nextCta, bundle
)
val bottomSheetData = viewModel.getLumpsumBottomSheetData()
bottomSheetData?.let {
val bsheetDataBundle =
Bundle().apply { putString(Constant.DATA, it) }
getBottomSheet(
SubPageStatusType.AMC_COMMON_BOTTOMSHEET,
bundle = bsheetDataBundle,
genericListener = ::processLumpsumPayment
)?.let { safelyShowBottomSheet(it, SubPageStatusType.AMC_COMMON_BOTTOMSHEET) }
} ?: run {
processLumpsumPayment(ActionData())
}
} else {
@@ -965,7 +951,41 @@ class FundBuyingFragmentV2 : AmcBaseFragment(), WidgetCallback {
}
}
private fun createSipRecurringAction(includeSipDate: Boolean) = View.OnClickListener {
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)) {
bundle.putString(Constant.PAYMENT_MODE, Constant.UPI)
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
)
)
} 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)
@@ -1014,8 +1034,8 @@ class FundBuyingFragmentV2 : AmcBaseFragment(), WidgetCallback {
}
}
private fun sipWeeklyFortnightlyAction() = createSipRecurringAction(false)
private fun sipMonthlyAction() = createSipRecurringAction(true)
private fun sipWeeklyFortnightlyAction(action: ActionData) = createSipRecurringAction(action, false)
private fun sipMonthlyAction(action: ActionData) = createSipRecurringAction(action, true)
override fun onAttach(context: Context) {
super.onAttach(context)

View File

@@ -367,24 +367,7 @@ class PaymentSummaryFragment : AmcBaseFragment() {
}
}
else {
val isOtpUrl = ctaUrl.endsWith(SubPageStatusType.OTP, true)
if (isOtpUrl) {
val bundle = Bundle().apply {
putString(ORDER_ID, arguments?.getString(ORDER_ID))
putString(PAYMENT_MODE, viewModel.paymentSelectedMode)
putString(AMOUNT, arguments?.getString(AMOUNT))
putString(ISIN, arguments?.getString(ISIN))
putString(
SIP_REFERENCE_ID,
arguments?.getString(SIP_REFERENCE_ID)
)
}
fragmentInterchangeListener?.navigateToNextScreen(
footer.nextCta,
bundle
)
} else {
if (handleOtpNavigation(footer.nextCta).not()) {
showLoader()
if (arguments?.getString(RETRY_PAYMENT).toBoolean()
.orFalse()
@@ -425,7 +408,7 @@ class PaymentSummaryFragment : AmcBaseFragment() {
private fun actionListener(action : ActionData){
sendEvent(eventsData = action.metaData?.clickedData)
when(action.url){
when(action.url) {
Constant.BACK_PRESS -> {
fragmentInterchangeListener?.navigateToNextScreen(null)
}
@@ -433,7 +416,9 @@ class PaymentSummaryFragment : AmcBaseFragment() {
//Do nothing
}
else -> {
fragmentInterchangeListener?.navigateToNextScreen(action)
if(handleOtpNavigation(action).not()) {
fragmentInterchangeListener?.navigateToNextScreen(action)
}
}
}
@@ -471,6 +456,29 @@ class PaymentSummaryFragment : AmcBaseFragment() {
fragmentInterchangeListener?.navigateToNextScreen(action)
}
private fun handleOtpNavigation(action: ActionData?): Boolean {
val isOtpUrl = action?.url.orEmpty().endsWith(SubPageStatusType.OTP, true)
if (isOtpUrl) {
val bundle = Bundle().apply {
putString(ORDER_ID, arguments?.getString(ORDER_ID))
putString(PAYMENT_MODE, viewModel.paymentSelectedMode)
putString(AMOUNT, arguments?.getString(AMOUNT))
putString(ISIN, arguments?.getString(ISIN))
putString(
SIP_REFERENCE_ID,
arguments?.getString(SIP_REFERENCE_ID)
)
}
fragmentInterchangeListener?.navigateToNextScreen(
action,
bundle
)
return true
}
return false
}
private fun moneyFormat(amount: String): String {
return if (amount.isNotBlank()) {
val formatter = DecimalFormat("##,##,##,##,###.##")

View File

@@ -9,6 +9,7 @@ package com.navi.amc.fundbuy.models
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import com.navi.amc.common.model.AmcCommonBottomSheetData
import com.navi.amc.common.model.Footer
import com.navi.amc.common.model.InformationCardData
import com.navi.base.model.ActionData
@@ -32,7 +33,8 @@ data class FundBuyData(
@SerializedName("fundHeader") val fundHeaderData: AmcHeaderData? = null,
@SerializedName("fundInvestmentType") val fundInvestmentType: FundInvestmentType? = null,
@SerializedName("note") val note: InformationCardData? = null,
@SerializedName("sipBottomSheet") val sipBottomSheet: GenericErrorResponse? = null,
@SerializedName("sipCommonBottomSheet") val sipCommonBottomSheet: AmcCommonBottomSheetData? = null,
@SerializedName("lumpsumCommonBottomSheet") val lumpsumCommonBottomSheet: AmcCommonBottomSheetData? = null,
@SerializedName("calendar") val calendarData: CalendarData? = null,
@SerializedName("rewards") val rewards: RewardsData? = null,
@SerializedName("footerVariations") val footerVariations: Map<String, Footer>? = null,

View File

@@ -12,6 +12,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.google.gson.Gson
import com.navi.amc.common.model.AdditionalDataAsyncResponse
import com.navi.amc.common.model.AmcCommonBottomSheetData
import com.navi.amc.common.model.Footer
import com.navi.amc.common.model.NextCtaResponse
import com.navi.amc.fundbuy.models.AmountPageFooter
@@ -30,10 +31,12 @@ import com.navi.amc.utils.Constant.MONTHLY
import com.navi.amc.utils.Constant.WEEKLY
import com.navi.amc.utils.updateCheckerResponse
import com.navi.base.model.ActionData
import com.navi.common.network.models.GenericErrorResponse
import com.navi.common.utils.EMPTY
import com.navi.common.utils.formatDateIntoPrefixMonthNamesWithYear
import com.navi.common.viewmodel.BaseVM
import com.navi.design.font.FontWeightEnum
import com.navi.design.textview.model.NaviSpan
import com.navi.design.textview.model.TextWithStyle
import com.navi.design.utils.parseDateFromOneToAnother
import com.navi.naviwidgets.models.response.CardType
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -160,20 +163,29 @@ class FundBuyV2ViewModel @Inject constructor(private val repository: FundBuyRepo
frequency: String? = null,
isAutoPayChecked: Boolean = false,
): String {
var message = fundBuyScreenData.value?.content?.sipBottomSheet?.message
message = message
?.replace("[AMOUNT]", amount.orEmpty())
?.replace("[FREQUENCY]", getFrequencyMessage(frequency.orEmpty()))
?.replace(
"[STARTING_DATE]",
getSipStartingDate(frequency, isAutoPayChecked)
var message = fundBuyScreenData.value?.content?.sipCommonBottomSheet?.subTitle?.text
val originalStyle = fundBuyScreenData.value?.content?.sipCommonBottomSheet?.subTitle?.style?.first()
val bsheetFormattedTitle =
restyleTextForSipCallout(
message.orEmpty(),
listOf("[AMOUNT]", "[FREQUENCY]", "[STARTING_DATE]"),
listOf(amount.orEmpty(), getFrequencyMessage(frequency.orEmpty()), getSipStartingDate(frequency, isAutoPayChecked)),
originalStyle
)
return gson.toJson(
fundBuyScreenData.value?.content?.sipBottomSheet?.copy(message = message),
GenericErrorResponse::class.java
fundBuyScreenData.value?.content?.sipCommonBottomSheet?.copy(subTitle = bsheetFormattedTitle),
AmcCommonBottomSheetData::class.java
)
}
fun getLumpsumBottomSheetData(): String? {
return fundBuyScreenData.value?.content?.lumpsumCommonBottomSheet?.let {
gson.toJson(it, AmcCommonBottomSheetData::class.java)
}
}
fun getCalendarData(): CalendarData? {
return _fundBuyScreenData.value?.content?.calendarData
}
@@ -267,6 +279,32 @@ class FundBuyV2ViewModel @Inject constructor(private val repository: FundBuyRepo
super.onCleared()
}
private fun restyleTextForSipCallout(
originalText: String,
placeHolder: List<String>,
replacedValue: List<String>,
span: NaviSpan?
): TextWithStyle {
var formattedText = originalText
val styleList = mutableListOf<NaviSpan>()
span?.let { styleList.add(it) }
placeHolder.forEachIndexed { index, _ ->
val startIndex = formattedText.indexOf(placeHolder[index])
val endIndex = startIndex + replacedValue[index].length
span?.let {
styleList.add(
it.copy(
startSpan = startIndex,
endSpan = endIndex,
fontName = FontWeightEnum.TT_MEDIUM.name
)
)
}
formattedText = formattedText.replace(placeHolder[index], replacedValue[index])
}
return TextWithStyle(text = formattedText, style = styleList)
}
companion object {
private const val DAY_IN_MILLIS = 24 * 60 * 60 * 1000L
}

View File

@@ -1,150 +0,0 @@
/*
*
* * Copyright © 2022-2023 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.amc.fundbuy.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.google.gson.Gson
import com.navi.amc.fundbuy.models.CalendarData
import com.navi.amc.fundbuy.models.FundBuyScreenData
import com.navi.amc.fundbuy.models.SipDetailsData
import com.navi.amc.fundbuy.models.SipDetailsResponse
import com.navi.amc.fundbuy.repository.FundBuyRepository
import com.navi.amc.utils.AmcAnalytics.DEFAULT
import com.navi.amc.utils.Constant
import com.navi.base.model.ActionData
import com.navi.common.network.models.GenericErrorResponse
import com.navi.common.viewmodel.BaseVM
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class FundBuyViewModel @Inject constructor(private val repository: FundBuyRepository) : BaseVM() {
private val _fundBuyScreenData = MutableLiveData<FundBuyScreenData?>()
val fundBuyScreenData: LiveData<FundBuyScreenData?>
get() = _fundBuyScreenData
private val _sipDetailsResponse = MutableLiveData<SipDetailsResponse?>()
val sipDetailsResponse: LiveData<SipDetailsResponse?>
get() = _sipDetailsResponse
var isSipSelected: Boolean? = null
var toHideDate: Boolean = false
var isSipTutorialCrossed: Boolean = false
var isLumpSumTutorialCrossed: Boolean = false
var sipType: String? = null
var selectedDate: String? = null
var toShowSipRecommended : Boolean = false
var toShowLumpsumRecommended : Boolean = false
var lastSipInteraction: String = DEFAULT
var lastLumpsumInteraction: String = DEFAULT
fun getFundBuyScreenData(isin: String) {
viewModelScope.launch {
val response = repository.getFundBuyDetail(isin)
if (response.error == null && response.errors.isNullOrEmpty()) {
_fundBuyScreenData.value = response.data
} else {
setErrorData(response.errors, response.error)
}
}
}
fun postSipDetails(details: SipDetailsData) {
viewModelScope.launch {
val response = repository.postSipDetails(details)
if (response.error == null && response.errors.isNullOrEmpty()) {
_sipDetailsResponse.value = response.data
} else {
setErrorData(response.errors, response.error)
}
}
}
fun getSipDisplayType(): String {
return when (sipType) {
"WEEKLY" -> "SIP - weekly"
"FORTNIGHTLY" -> "SIP - fortnightly"
"MONTHLY" -> "SIP - monthly"
else -> ""
}
}
private fun getFrequencyName(frequency: String): String {
return when (frequency) {
"WEEKLY" -> "week"
"FORTNIGHTLY" -> "fortnight"
"MONTHLY" -> "month"
else -> ""
}
}
fun getSipBottomSheetData(amount: String, frequency: String): String {
val message =
String.format(
_fundBuyScreenData.value?.content?.sipBottomSheet?.message.orEmpty(),
amount,
getFrequencyName(frequency)
)
return Gson()
.toJson(
fundBuyScreenData.value?.content?.sipBottomSheet?.copy(message = message),
GenericErrorResponse::class.java
)
}
fun getCalendarData(): CalendarData? {
return _fundBuyScreenData.value?.content?.calendarData
}
fun isAutoPayThere(): Boolean {
return _fundBuyScreenData.value?.content?.fundInvestmentType?.autoPay != null
}
fun onChipSelected(current: String?, chip: String?, action: ActionData?): String {
val currentAmount = current?.toDoubleOrNull() ?: 0.0
val chipAmount = chip?.toDoubleOrNull() ?: 0.0
return when(action?.url) {
Constant.ADD_AMOUNT -> {
(currentAmount + chipAmount).toString()
}
else -> {
chip.orEmpty()
}
}
}
override fun onCleared() {
isSipSelected = null
toHideDate = false
isSipTutorialCrossed = false
isLumpSumTutorialCrossed = false
sipType = null
selectedDate = null
errorResponse.value = null
_fundBuyScreenData.value = null
_sipDetailsResponse.value = null
toShowSipRecommended = false
toShowLumpsumRecommended = false
lastSipInteraction = DEFAULT
lastLumpsumInteraction = DEFAULT
super.onCleared()
}
}

View File

@@ -41,19 +41,53 @@
app:layout_constraintTop_toBottomOf="@id/title" />
<com.navi.design.textview.NaviTextView
android:id="@+id/primarybtn"
style="@style/ActionButtonText6Style"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_48"
android:layout_marginStart="@dimen/dp_16"
android:layout_marginTop="@dimen/dp_32"
android:layout_marginEnd="@dimen/dp_16"
android:background="@drawable/bg_cta_primary_purple_amc_rounded_4"
android:gravity="center"
android:id="@+id/note_banner"
android:layout_width="@dimen/dp_0"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/dp_16"
android:layout_marginTop="@dimen/dp_16"
android:paddingVertical="@dimen/dp_8"
android:paddingHorizontal="@dimen/dp_16"
android:lineSpacingExtra="@dimen/dp_4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/subtitle"
tools:text="Exit" />
app:layout_constraintTop_toBottomOf="@id/subtitle" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/btn_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="@integer/integer_2"
android:paddingEnd="@dimen/dp_16"
android:layout_marginTop="@dimen/dp_24"
app:layout_goneMarginTop="@dimen/dp_32"
app:layout_constraintTop_toBottomOf="@id/note_banner">
<com.navi.design.textview.NaviTextView
android:id="@+id/secondary_side_btn"
style="@style/ActionButtonText10Style"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_48"
android:layout_marginStart="@dimen/dp_16"
android:layout_weight="1"
android:background="@drawable/bg_cta_secondary_amc_rounded_4"
android:gravity="center"
android:textColor="@color/purple_button_color"
tools:text="Exit" />
<com.navi.design.textview.NaviTextView
android:id="@+id/primarybtn"
style="@style/ActionButtonText6Style"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_48"
android:layout_marginStart="@dimen/dp_16"
android:layout_weight="1"
android:background="@drawable/bg_cta_primary_purple_amc_rounded_4"
android:gravity="center"
tools:text="Exit" />
</androidx.appcompat.widget.LinearLayoutCompat>
<com.navi.design.textview.NaviTextView
android:id="@+id/secondarybtn"
@@ -70,7 +104,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/primarybtn"
app:layout_constraintTop_toBottomOf="@id/btn_container"
tools:text="Exit" />
</androidx.constraintlayout.widget.ConstraintLayout>