NTP-71871 | Fund details SIP simulator (#16837)
This commit is contained in:
@@ -0,0 +1,474 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.amc.fundbuy.composables.widgets
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
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.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.SliderDefaults
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.navi.amc.fundbuy.models.ReturnsCalculatorData
|
||||
import com.navi.amc.utils.AmcColor
|
||||
import com.navi.amc.utils.AmcColor.rippleColor
|
||||
import com.navi.amc.utils.Constant.DEFAULT
|
||||
import com.navi.amc.utils.Constant.SELECTED
|
||||
import com.navi.amc.utils.calculateReturns
|
||||
import com.navi.amc.utils.convertToTextFieldData
|
||||
import com.navi.design.font.getFontWeight
|
||||
import com.navi.design.font.naviFontFamily
|
||||
import com.navi.design.textview.model.NaviSpan
|
||||
import com.navi.design.textview.model.TextWithStyle
|
||||
import com.navi.design.utils.NoRippleIndicationSource
|
||||
import com.navi.design.utils.decimalFormatForAmount
|
||||
import com.navi.naviwidgets.composewidget.reusable.colorCTAPrimary
|
||||
import com.navi.naviwidgets.composewidget.reusable.colorOffWhite
|
||||
import com.navi.naviwidgets.composewidget.reusable.colorShadow
|
||||
import com.navi.naviwidgets.composewidget.reusable.colorTextPrimary
|
||||
import com.navi.naviwidgets.extensions.NaviImage
|
||||
import com.navi.naviwidgets.extensions.NaviText
|
||||
import com.navi.naviwidgets.extensions.NaviTextWidgetized
|
||||
import com.navi.naviwidgets.models.response.ImageFieldData
|
||||
import com.navi.uitron.utils.hexToComposeColor
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ReturnsCalculatorComposable(calculatorData: ReturnsCalculatorData? = null) {
|
||||
calculatorData?.let { data ->
|
||||
var isExpanded by remember { mutableStateOf(data.isExpanded == true) }
|
||||
var selectedDuration by remember { mutableStateOf(data.duration?.defaultSelected) }
|
||||
var sliderValue by remember { mutableLongStateOf(data.sliderData?.selectedValue ?: 20000) }
|
||||
|
||||
val (cagr, durationInMonths, returnsPercentage) =
|
||||
remember(selectedDuration) {
|
||||
val item = data.duration?.items?.find { it.key == selectedDuration }
|
||||
Triple(item?.cagrValue ?: 0.0, item?.durationInMonths ?: 1, item?.returns)
|
||||
}
|
||||
|
||||
val totalInvestment =
|
||||
remember(sliderValue, durationInMonths) { sliderValue.toLong() * durationInMonths }
|
||||
|
||||
val projectedAmount =
|
||||
remember(sliderValue, cagr, durationInMonths) {
|
||||
calculateReturns(
|
||||
monthlyAmount = sliderValue.toDouble(),
|
||||
cagrInPercentage = cagr,
|
||||
durationInMonths = durationInMonths,
|
||||
)
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier.fillMaxWidth().clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { NoRippleIndicationSource() },
|
||||
) {
|
||||
isExpanded = !isExpanded
|
||||
},
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
data.title?.let { title ->
|
||||
NaviTextWidgetized(textFieldData = convertToTextFieldData(title))
|
||||
}
|
||||
|
||||
val rotationState by
|
||||
animateFloatAsState(
|
||||
targetValue = if (isExpanded) 180f else 0f,
|
||||
animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing),
|
||||
)
|
||||
|
||||
NaviImage(
|
||||
imageFieldData = ImageFieldData(url = data.rightChevronDown),
|
||||
modifier = Modifier.size(24.dp).rotate(rotationState),
|
||||
)
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = isExpanded,
|
||||
enter =
|
||||
expandVertically(
|
||||
animationSpec = tween(durationMillis = 400),
|
||||
expandFrom = Alignment.Top,
|
||||
),
|
||||
exit =
|
||||
shrinkVertically(
|
||||
animationSpec = tween(durationMillis = 400),
|
||||
shrinkTowards = Alignment.Top,
|
||||
),
|
||||
) {
|
||||
Column {
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
data.duration?.title?.let { title ->
|
||||
NaviTextWidgetized(textFieldData = convertToTextFieldData(title))
|
||||
}
|
||||
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
data.duration?.items?.forEach { item ->
|
||||
val isSelected = item.key == selectedDuration
|
||||
val spanList =
|
||||
if (isSelected) {
|
||||
item.title?.styleVariations?.get(SELECTED)
|
||||
} else {
|
||||
item.title?.styleVariations?.get(DEFAULT)
|
||||
}
|
||||
val span = spanList?.firstOrNull()
|
||||
DurationChip(
|
||||
text = item.title?.text.orEmpty(),
|
||||
isSelected = isSelected,
|
||||
styleSpan = span,
|
||||
onClick = { selectedDuration = item.key ?: "1Y" },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
SliderSection(
|
||||
data = data,
|
||||
sliderValue = sliderValue,
|
||||
onValueChange = { newValue -> sliderValue = newValue },
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
NoteSection(
|
||||
data = data,
|
||||
totalInvestment = totalInvestment,
|
||||
projectedAmount = projectedAmount,
|
||||
returns = returnsPercentage,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SliderSection(
|
||||
data: ReturnsCalculatorData,
|
||||
sliderValue: Long,
|
||||
onValueChange: (Long) -> Unit = {},
|
||||
) {
|
||||
val layoutWidthPx = remember { mutableStateOf<Float?>(null) }
|
||||
val minSliderValue = data.sliderData?.minValue?.toFloat() ?: 100f
|
||||
val maxSliderValue = data.sliderData?.maxValue?.toFloat() ?: 100000f
|
||||
val sliderStepValue = data.sliderData?.stepValue?.toInt() ?: 100
|
||||
val interactionSource = remember { NoRippleIndicationSource() }
|
||||
|
||||
val thumbPosition =
|
||||
((sliderValue.toFloat() - minSliderValue) / (maxSliderValue - minSliderValue)).coerceIn(
|
||||
0f,
|
||||
1f,
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier =
|
||||
Modifier.fillMaxWidth().onGloballyPositioned { coordinates ->
|
||||
layoutWidthPx.value = coordinates.size.width.toFloat()
|
||||
}
|
||||
) {
|
||||
Slider(
|
||||
value = sliderValue.toFloat(),
|
||||
onValueChange = {
|
||||
val normalizedValue = (it - minSliderValue) / (maxSliderValue - minSliderValue)
|
||||
val customValue =
|
||||
when {
|
||||
normalizedValue < 0.01f -> minSliderValue.toLong()
|
||||
else -> {
|
||||
val stepIndex = ((normalizedValue - 0.01f) / 0.99f * 99).roundToLong()
|
||||
1000L + stepIndex * 1000L
|
||||
}
|
||||
}
|
||||
onValueChange(customValue)
|
||||
},
|
||||
valueRange = minSliderValue..maxSliderValue,
|
||||
steps = ((maxSliderValue - minSliderValue) / sliderStepValue).toInt() - 1,
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 20.dp),
|
||||
colors =
|
||||
SliderDefaults.colors(
|
||||
thumbColor = AmcColor.purplePrimary,
|
||||
activeTrackColor =
|
||||
data.sliderData?.minTrackTintColor?.hexToComposeColor
|
||||
?: AmcColor.purplePrimary,
|
||||
inactiveTrackColor =
|
||||
data.sliderData?.maxTrackTintColor?.hexToComposeColor
|
||||
?: AmcColor.borderDefault,
|
||||
activeTickColor = Color.Transparent,
|
||||
inactiveTickColor = Color.Transparent,
|
||||
),
|
||||
thumb = {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier.shadow(
|
||||
elevation = 4.dp,
|
||||
shape = CircleShape,
|
||||
spotColor = colorTextPrimary,
|
||||
ambientColor = colorTextPrimary,
|
||||
)
|
||||
.size(24.dp)
|
||||
.background(Color.White, shape = CircleShape),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier.size(20.dp)
|
||||
.background(AmcColor.purplePrimary, shape = CircleShape)
|
||||
)
|
||||
}
|
||||
},
|
||||
track = { sliderPositions ->
|
||||
val fraction by remember {
|
||||
derivedStateOf {
|
||||
(sliderPositions.value - sliderPositions.valueRange.start) /
|
||||
(sliderPositions.valueRange.endInclusive -
|
||||
sliderPositions.valueRange.start)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier =
|
||||
Modifier.shadow(
|
||||
elevation = 4.dp,
|
||||
spotColor = colorShadow,
|
||||
ambientColor = colorShadow,
|
||||
)
|
||||
.fillMaxWidth()
|
||||
.height(4.dp)
|
||||
.background(
|
||||
data.sliderData?.maxTrackTintColor?.hexToComposeColor
|
||||
?: AmcColor.borderDefault,
|
||||
shape = RoundedCornerShape(2.dp),
|
||||
)
|
||||
)
|
||||
Box(
|
||||
modifier =
|
||||
Modifier.shadow(
|
||||
elevation = 4.dp,
|
||||
spotColor = colorShadow,
|
||||
ambientColor = colorShadow,
|
||||
)
|
||||
.fillMaxWidth(fraction)
|
||||
.height(4.dp)
|
||||
.background(
|
||||
data.sliderData?.minTrackTintColor?.hexToComposeColor
|
||||
?: AmcColor.purplePrimary,
|
||||
shape = RoundedCornerShape(2.dp),
|
||||
)
|
||||
)
|
||||
},
|
||||
interactionSource = interactionSource,
|
||||
)
|
||||
|
||||
if (layoutWidthPx.value != null) {
|
||||
val density = LocalDensity.current
|
||||
val layoutWidth = layoutWidthPx.value!!
|
||||
|
||||
val thumbRadiusPx = with(density) { 12.dp.toPx() }
|
||||
val toolTipData = convertToTextFieldData(data.sliderData?.toolTipData)
|
||||
val formattedSliderValue = decimalFormatForAmount.format(sliderValue)
|
||||
toolTipData?.text = "₹${formattedSliderValue}/month"
|
||||
|
||||
val tooltipWidth =
|
||||
when {
|
||||
sliderValue >= 100000 -> 140.dp
|
||||
sliderValue >= 1000 -> 130.dp
|
||||
else -> 110.dp
|
||||
}
|
||||
|
||||
val tooltipWidthPx = with(density) { tooltipWidth.toPx() }
|
||||
val adjustedTrackWidth = layoutWidth - 2 * thumbRadiusPx
|
||||
val thumbCenterX = thumbRadiusPx + thumbPosition * adjustedTrackWidth
|
||||
|
||||
val tooltipHalfWidth = tooltipWidthPx / 2
|
||||
val tooltipCenterX =
|
||||
thumbCenterX.coerceIn(tooltipHalfWidth, layoutWidth - tooltipHalfWidth)
|
||||
val tooltipLeft = tooltipCenterX - tooltipHalfWidth
|
||||
|
||||
val triangleWidthPx = with(density) { 10.dp.toPx() }
|
||||
val triangleOffsetX =
|
||||
(thumbCenterX - tooltipLeft - triangleWidthPx / 2).coerceIn(
|
||||
0f,
|
||||
tooltipWidthPx - triangleWidthPx,
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier =
|
||||
Modifier.offset(x = with(density) { tooltipLeft.toDp() }, y = (-4).dp)
|
||||
.width(with(density) { tooltipWidthPx.toDp() })
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier.align(Alignment.CenterHorizontally)
|
||||
.background(colorCTAPrimary, shape = RoundedCornerShape(4.dp))
|
||||
.padding(horizontal = 8.dp, vertical = 6.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
NaviTextWidgetized(toolTipData, modifier = Modifier.align(Alignment.Center))
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
Canvas(
|
||||
modifier =
|
||||
Modifier.size(10.dp, 5.dp)
|
||||
.offset(x = with(density) { triangleOffsetX.toDp() })
|
||||
) {
|
||||
val path =
|
||||
Path().apply {
|
||||
moveTo(0f, 0f)
|
||||
lineTo(size.width, 0f)
|
||||
lineTo(size.width / 2f, size.height)
|
||||
close()
|
||||
}
|
||||
drawPath(path, color = colorCTAPrimary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NoteSection(
|
||||
data: ReturnsCalculatorData? = null,
|
||||
totalInvestment: Long = 0,
|
||||
projectedAmount: Double = 0.0,
|
||||
returns: TextWithStyle? = null,
|
||||
) {
|
||||
val formattedInvestment by
|
||||
remember(totalInvestment) { mutableStateOf(decimalFormatForAmount.format(totalInvestment)) }
|
||||
|
||||
val formattedProjectedAmount by
|
||||
remember(projectedAmount) {
|
||||
mutableStateOf(decimalFormatForAmount.format(projectedAmount.roundToLong()))
|
||||
}
|
||||
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(containerColor = colorOffWhite),
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
elevation = CardDefaults.cardElevation(0.dp),
|
||||
) {
|
||||
Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp)) {
|
||||
val noteData = data?.investmentNote
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
NaviTextWidgetized(textFieldData = convertToTextFieldData(noteData?.startText))
|
||||
val investedAmountData =
|
||||
convertToTextFieldData(noteData?.investedAmount)?.apply {
|
||||
text = "₹$formattedInvestment"
|
||||
}
|
||||
NaviTextWidgetized(textFieldData = investedAmountData)
|
||||
Spacer(modifier = Modifier.width(2.dp))
|
||||
NaviTextWidgetized(textFieldData = convertToTextFieldData(noteData?.endText))
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
val projectedAmountData =
|
||||
convertToTextFieldData(noteData?.projectedAmount)?.apply {
|
||||
text = "₹$formattedProjectedAmount"
|
||||
}
|
||||
NaviTextWidgetized(textFieldData = projectedAmountData)
|
||||
Spacer(modifier = Modifier.width(3.dp))
|
||||
NaviTextWidgetized(textFieldData = convertToTextFieldData(returns))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DurationChip(
|
||||
text: String,
|
||||
styleSpan: NaviSpan? = null,
|
||||
isSelected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier.clip(RoundedCornerShape(20.dp))
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = if (isSelected) colorCTAPrimary else Color.Transparent,
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
)
|
||||
.background(Color.Transparent)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = ripple(color = rippleColor),
|
||||
) {
|
||||
onClick()
|
||||
}
|
||||
.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
NaviText(
|
||||
text = text,
|
||||
fontFamily = naviFontFamily,
|
||||
color =
|
||||
styleSpan?.spanColor?.hexToComposeColor
|
||||
?: if (isSelected) colorCTAPrimary else Color.Transparent,
|
||||
fontSize = (styleSpan?.fontSize?.toFloat() ?: 12f).sp,
|
||||
fontWeight = getFontWeight(styleSpan?.fontName),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewStub
|
||||
import android.widget.ImageView
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
@@ -35,6 +36,7 @@ import com.navi.amc.compose.feature.ftue.model.FundSelectionData
|
||||
import com.navi.amc.databinding.DownloadDocumentLayoutBinding
|
||||
import com.navi.amc.databinding.FundDetailScreenLayoutBinding
|
||||
import com.navi.amc.databinding.InvestUspLayoutBinding
|
||||
import com.navi.amc.fundbuy.composables.widgets.ReturnsCalculatorComposable
|
||||
import com.navi.amc.fundbuy.models.AmcHeaderData
|
||||
import com.navi.amc.fundbuy.models.FundReturn
|
||||
import com.navi.amc.fundbuy.viewmodel.FundBuyFlowViewModel
|
||||
@@ -367,6 +369,14 @@ class FundDetailsFragment : AmcBaseFragment(), FooterInteractionListener {
|
||||
}
|
||||
}
|
||||
|
||||
fundDetailScreenData?.content?.returnsCalculatorDetails?.let { calculatorData ->
|
||||
val calculatorView =
|
||||
ComposeView(context).apply {
|
||||
setContent { ReturnsCalculatorComposable(calculatorData) }
|
||||
}
|
||||
addView(calculatorView)
|
||||
}
|
||||
|
||||
fundDetailScreenData?.content?.usp?.let {
|
||||
val childBinding =
|
||||
DataBindingUtil.inflate<InvestUspLayoutBinding>(
|
||||
|
||||
@@ -12,9 +12,11 @@ import com.google.gson.annotations.SerializedName
|
||||
import com.navi.amc.common.model.Footer
|
||||
import com.navi.amc.common.model.FundInvestmentDetailData
|
||||
import com.navi.amc.common.model.InformationCardData
|
||||
import com.navi.amc.fundbuy.models.cards.SliderData
|
||||
import com.navi.common.model.Header
|
||||
import com.navi.design.textview.model.NaviSpan
|
||||
import com.navi.design.textview.model.TextWithStyle
|
||||
import com.navi.design.textview.model.TextWithStyleVariation
|
||||
import com.navi.naviwidgets.models.FundDetailData
|
||||
import com.navi.naviwidgets.models.response.amc.TagData
|
||||
import java.io.Serializable
|
||||
@@ -44,6 +46,41 @@ data class FundDetailScreenData(
|
||||
@SerializedName("fundInvestmentDetails")
|
||||
val fundInvestmentDetails: FundInvestmentDetailData? = null,
|
||||
@SerializedName("regulatoryInfo") val regulatoryInfo: RegulatoryInfoData? = null,
|
||||
@SerializedName("returnsCalculatorDetails")
|
||||
val returnsCalculatorDetails: ReturnsCalculatorData? = null,
|
||||
)
|
||||
|
||||
data class ReturnsCalculatorData(
|
||||
@SerializedName("title") val title: TextWithStyle? = null,
|
||||
@SerializedName("isExpanded") val isExpanded: Boolean? = false,
|
||||
@SerializedName("rightChevronDown") val rightChevronDown: String? = null,
|
||||
@SerializedName("rightChevronUp") val rightChevronUp: String? = null,
|
||||
@SerializedName("sliderData") val sliderData: SliderData? = null,
|
||||
@SerializedName("duration") val duration: DurationChipData? = null,
|
||||
@SerializedName("investmentNote") val investmentNote: NoteData? = null,
|
||||
)
|
||||
|
||||
data class NoteData(
|
||||
@SerializedName("startText") val startText: TextWithStyle? = null,
|
||||
@SerializedName("endText") val endText: TextWithStyle? = null,
|
||||
@SerializedName("investedAmount") val investedAmount: TextWithStyle? = null,
|
||||
@SerializedName("projectedAmount") val projectedAmount: TextWithStyle? = null,
|
||||
@SerializedName("returns") val returns: TextWithStyle? = null,
|
||||
)
|
||||
|
||||
data class DurationChipData(
|
||||
@SerializedName("title") val title: TextWithStyle? = null,
|
||||
@SerializedName("defaultSelected") val defaultSelected: String? = null,
|
||||
@SerializedName("items") val items: List<ChipItemData>? = null,
|
||||
)
|
||||
|
||||
data class ChipItemData(
|
||||
@SerializedName("title") val title: TextWithStyleVariation? = null,
|
||||
@SerializedName("key") val key: String? = null,
|
||||
@SerializedName("isSelected") val isSelected: Boolean? = null,
|
||||
@SerializedName("cagrValue") val cagrValue: Double? = null,
|
||||
@SerializedName("durationInMonths") val durationInMonths: Int? = null,
|
||||
@SerializedName("returns") val returns: TextWithStyle? = null,
|
||||
)
|
||||
|
||||
@Parcelize
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.google.gson.annotations.SerializedName
|
||||
import com.navi.amc.fundbuy.models.widgets.TrackerContent
|
||||
import com.navi.base.model.ActionData
|
||||
import com.navi.common.model.InvestmentBaseProperty
|
||||
import com.navi.design.textview.model.TextWithStyle
|
||||
import com.navi.naviwidgets.models.response.ImageFieldData
|
||||
import com.navi.naviwidgets.models.response.TextFieldData
|
||||
|
||||
@@ -70,4 +71,5 @@ data class SliderData(
|
||||
@SerializedName("minTrackTintColor") val minTrackTintColor: String? = null,
|
||||
@SerializedName("maxTrackTintColor") val maxTrackTintColor: String? = null,
|
||||
@SerializedName("title") val title: TextFieldData? = null,
|
||||
@SerializedName("toolTipData") val toolTipData: TextWithStyle? = null,
|
||||
)
|
||||
|
||||
@@ -21,4 +21,5 @@ object AmcColor {
|
||||
val textInputNonEditable = Color(0xFF6B6B6B)
|
||||
val textInputPrimary = Color(0xFF191919)
|
||||
val purplePrimary = Color(0xFF3C0050)
|
||||
val rippleColor = Color.Black.copy(alpha = 0.3f)
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ import java.text.SimpleDateFormat
|
||||
import java.util.TimeZone
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.max
|
||||
import kotlin.math.pow
|
||||
|
||||
fun calculateInvestmentReturnsAmount(
|
||||
investmentType: String,
|
||||
@@ -237,3 +238,18 @@ fun shouldCallUpdateSip(url: String?): Boolean {
|
||||
url?.contains(RESUMED_INSTALLMENT).orFalse() ||
|
||||
url?.contains(GOAL_SIP_DELETED).orFalse()
|
||||
}
|
||||
|
||||
fun calculateReturns(
|
||||
monthlyAmount: Double,
|
||||
cagrInPercentage: Double,
|
||||
durationInMonths: Int,
|
||||
): Double {
|
||||
if (cagrInPercentage == 0.0) {
|
||||
return monthlyAmount * durationInMonths
|
||||
}
|
||||
val annualRate = cagrInPercentage / 100
|
||||
val monthlyRateOfInterest = (1 + annualRate).pow(1.0 / 12) - 1
|
||||
return monthlyAmount *
|
||||
((((1 + monthlyRateOfInterest).pow(durationInMonths)) - 1) / monthlyRateOfInterest) *
|
||||
(1 + monthlyRateOfInterest)
|
||||
}
|
||||
|
||||
@@ -265,4 +265,6 @@ object Constant {
|
||||
const val VALUE = "value"
|
||||
const val SIP_TYPE_CAMEL_CASE = "sipType"
|
||||
const val SHOW_TOAST_MESSAGE = "showToastMessage"
|
||||
const val SELECTED = "selected"
|
||||
const val DEFAULT = "default"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user