From 03ea9fdb0278dec7cf468bc67c243f3a7bf782d5 Mon Sep 17 00:00:00 2001 From: A Shrihari Raju Date: Fri, 25 Apr 2025 13:04:34 +0530 Subject: [PATCH] NTP-55320 || Shrihari | FTUE v0 (#15820) Co-authored-by: Aman S --- .../InvestmentTabWidgetJsonDeserializer.kt | 5 +- .../ExploreMoreWidget.kt | 1 + .../response/InvestmentTabWidgetType.kt | 1 + ...nvestmentGenericComposableWidgetFactory.kt | 13 +- .../investmentTab/InvestmentsScreenHelper.kt | 22 ++ .../investmentTab/VisibilityTracker.kt | 2 +- .../FtueTrackerCardComposable.kt | 178 +++++++++++++ .../widgets/ExploreMoreWidgetComposable.kt | 12 +- .../FtueWithTrackerWidgetComposable.kt | 213 ++++++++++++++++ .../widgets/RepeatOrderWidgetComposable.kt | 2 +- android/navi-amc/build.gradle | 2 + android/navi-amc/src/main/AndroidManifest.xml | 7 + .../composables/AmcCardListComposable.kt | 19 +- .../AmcCommonWidgetListRenderer.kt | 3 + .../composables/AmcComposableWidgetFactory.kt | 29 ++- .../widgets/SpacerWidgetComposable.kt | 10 +- .../common/model/AmcWidgetJsonDeserializer.kt | 8 + .../navi/amc/common/model/AmcWidgetType.kt | 3 + .../java/com/navi/amc/common/model/Footer.kt | 9 + .../amc/common/model/widgets/SpacerWidget.kt | 5 +- .../common/model/AmcGenericScreenResponse.kt | 19 ++ .../amc/compose/common/model/ScreenState.kt | 20 ++ .../amc/compose/common/ui/AmcRouterScreen.kt | 75 ++++++ .../navi/amc/compose/common/ui/AmcShimmer.kt | 51 ++++ .../com/navi/amc/compose/common/ui/Ext.kt | 151 ++++++++++++ .../amc/compose/common/ui/NaviAmcFooter.kt | 105 ++++++++ .../amc/compose/common/ui/NaviAmcHeader.kt | 112 +++++++++ .../amc/compose/common/utils/NaviAmcScreen.kt | 15 ++ .../amc/compose/common/utils/NavigationExt.kt | 52 ++++ .../amc/compose/entry/AmcComposeActivity.kt | 89 +++++++ .../amc/compose/entry/AmcComposeViewModel.kt | 18 ++ .../navi/amc/compose/entry/AmcMainScreen.kt | 95 +++++++ .../amc/compose/entry/BackButtonHandler.kt | 44 ++++ .../navi/amc/compose/entry/NaviAmcRouter.kt | 59 +++++ .../compose/feature/ftue/FtueRepository.kt | 40 +++ .../feature/ftue/model/FundSelectionData.kt | 12 + .../feature/ftue/ui/FtueEducateScreen.kt | 159 ++++++++++++ .../feature/ftue/ui/FtueFundSelectScreen.kt | 190 ++++++++++++++ .../feature/ftue/viewmodel/FtueViewModel.kt | 105 ++++++++ .../cards/PaymentCardComposable.kt | 233 ++++++++++++++++++ .../{ => widgets}/BannerWidgetComposable.kt | 20 +- .../BannerWithLottieWidgetComposable.kt | 61 +++++ .../{ => widgets}/FooterWidgetComposable.kt | 2 +- .../{ => widgets}/HelpInfoWidgetComposable.kt | 2 +- .../OnTimeAssuranceWidgetComposable.kt | 3 +- .../widgets/RepeatOrderWidgetComposable.kt | 101 ++++++++ .../TitleContentRadioWidgetComposable.kt | 183 ++++++++++++++ .../navi/amc/fundbuy/models/AmcHeaderData.kt | 2 + .../amc/fundbuy/models/cards}/PaymentCard.kt | 28 +++ .../models/widgets/FtueWithTrackerWidget.kt | 81 ++++++ .../models/widgets}/RepeatOrderWidget.kt | 15 +- .../models/widgets/TitleContentRadioWidget.kt | 57 +++++ .../widgets/TitleSubtitleImageWidget.kt | 3 +- .../widgets/TitleSubtitleLottieWidget.kt | 38 +++ .../amc/navigator/NaviAmcDeeplinkNavigator.kt | 7 +- .../amc/network/retrofit/RetrofitService.kt | 13 + .../main/java/com/navi/amc/utils/Constant.kt | 6 + 57 files changed, 2782 insertions(+), 28 deletions(-) create mode 100644 android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/genericComposables/FtueTrackerCardComposable.kt create mode 100644 android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/widgets/FtueWithTrackerWidgetComposable.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/compose/common/model/AmcGenericScreenResponse.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/compose/common/model/ScreenState.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/compose/common/ui/AmcRouterScreen.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/compose/common/ui/AmcShimmer.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/compose/common/ui/Ext.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/compose/common/ui/NaviAmcFooter.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/compose/common/ui/NaviAmcHeader.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/compose/common/utils/NaviAmcScreen.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/compose/common/utils/NavigationExt.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/compose/entry/AmcComposeActivity.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/compose/entry/AmcComposeViewModel.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/compose/entry/AmcMainScreen.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/compose/entry/BackButtonHandler.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/compose/entry/NaviAmcRouter.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/compose/feature/ftue/FtueRepository.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/compose/feature/ftue/model/FundSelectionData.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/compose/feature/ftue/ui/FtueEducateScreen.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/compose/feature/ftue/ui/FtueFundSelectScreen.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/compose/feature/ftue/viewmodel/FtueViewModel.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/cards/PaymentCardComposable.kt rename android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/{ => widgets}/BannerWidgetComposable.kt (73%) create mode 100644 android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/BannerWithLottieWidgetComposable.kt rename android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/{ => widgets}/FooterWidgetComposable.kt (97%) rename android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/{ => widgets}/HelpInfoWidgetComposable.kt (98%) rename android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/{ => widgets}/OnTimeAssuranceWidgetComposable.kt (98%) create mode 100644 android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/RepeatOrderWidgetComposable.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/TitleContentRadioWidgetComposable.kt rename android/{app/src/main/java/com/naviapp/home/dashboard/models/investmentTabWidgetData/common => navi-amc/src/main/java/com/navi/amc/fundbuy/models/cards}/PaymentCard.kt (55%) create mode 100644 android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/widgets/FtueWithTrackerWidget.kt rename android/{app/src/main/java/com/naviapp/home/dashboard/models/investmentTabWidgetData => navi-amc/src/main/java/com/navi/amc/fundbuy/models/widgets}/RepeatOrderWidget.kt (65%) create mode 100644 android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/widgets/TitleContentRadioWidget.kt create mode 100644 android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/widgets/TitleSubtitleLottieWidget.kt diff --git a/android/app/src/main/java/com/naviapp/home/dashboard/models/deserializer/InvestmentTabWidgetJsonDeserializer.kt b/android/app/src/main/java/com/naviapp/home/dashboard/models/deserializer/InvestmentTabWidgetJsonDeserializer.kt index 9c4b45b25d..b84a2b33f5 100644 --- a/android/app/src/main/java/com/naviapp/home/dashboard/models/deserializer/InvestmentTabWidgetJsonDeserializer.kt +++ b/android/app/src/main/java/com/naviapp/home/dashboard/models/deserializer/InvestmentTabWidgetJsonDeserializer.kt @@ -13,6 +13,8 @@ import com.google.gson.JsonDeserializer import com.google.gson.JsonElement import com.navi.amc.common.model.GenericComposableWidget import com.navi.amc.common.model.widgets.SpacerWidget +import com.navi.amc.fundbuy.models.widgets.FtueWithTrackerWidget +import com.navi.amc.fundbuy.models.widgets.RepeatOrderWidget import com.navi.amc.fundbuy.models.widgets.RiskFreeFundWidget import com.navi.amc.fundbuy.models.widgets.SetupMonthlyTargetWidget import com.naviapp.home.dashboard.models.investmentTabWidgetData.ActionCardWidget @@ -24,7 +26,6 @@ import com.naviapp.home.dashboard.models.investmentTabWidgetData.HighestReturnFu import com.naviapp.home.dashboard.models.investmentTabWidgetData.MonthlyInvestmentGoalWidget import com.naviapp.home.dashboard.models.investmentTabWidgetData.OrdersInProgressWidget import com.naviapp.home.dashboard.models.investmentTabWidgetData.PortfolioWidget -import com.naviapp.home.dashboard.models.investmentTabWidgetData.RepeatOrderWidget import com.naviapp.home.dashboard.models.investmentTabWidgetData.RewardNudgeWidget import com.naviapp.home.dashboard.models.investmentTabWidgetData.SipAutoPayNudgeWidget import com.naviapp.home.dashboard.models.investmentTabWidgetData.TopInvestingFundsWidget @@ -76,6 +77,8 @@ class InvestmentTabWidgetJsonDeserializer : JsonDeserializer SetupMonthlyTargetWidget::class.java + InvestmentTabWidgetType.FTUE_WITH_TRACKER_WIDGET.value -> + FtueWithTrackerWidget::class.java else -> null } return if (className != null) { diff --git a/android/app/src/main/java/com/naviapp/home/dashboard/models/investmentTabWidgetData/ExploreMoreWidget.kt b/android/app/src/main/java/com/naviapp/home/dashboard/models/investmentTabWidgetData/ExploreMoreWidget.kt index 47bea42b4c..28ca739d57 100644 --- a/android/app/src/main/java/com/naviapp/home/dashboard/models/investmentTabWidgetData/ExploreMoreWidget.kt +++ b/android/app/src/main/java/com/naviapp/home/dashboard/models/investmentTabWidgetData/ExploreMoreWidget.kt @@ -39,6 +39,7 @@ data class ExploreMoreSectionData( data class TitleWithIconsCard( @SerializedName("property") val property: InvestmentBaseProperty? = null, + @SerializedName("titleProperty") val titleProperty: InvestmentBaseProperty? = null, @SerializedName("title") val title: TextFieldData? = null, @SerializedName("leftIcon") val leftIcon: ImageFieldData? = null, @SerializedName("rightIcon") val rightIcon: ImageFieldData? = null, diff --git a/android/app/src/main/java/com/naviapp/home/dashboard/models/response/InvestmentTabWidgetType.kt b/android/app/src/main/java/com/naviapp/home/dashboard/models/response/InvestmentTabWidgetType.kt index b6613c1bbb..686d06f741 100644 --- a/android/app/src/main/java/com/naviapp/home/dashboard/models/response/InvestmentTabWidgetType.kt +++ b/android/app/src/main/java/com/naviapp/home/dashboard/models/response/InvestmentTabWidgetType.kt @@ -27,4 +27,5 @@ enum class InvestmentTabWidgetType(val value: String) { SIP_AUTOPAY_NUDGE_WIDGET("sip_autopay_nudge_widget"), MONTHLY_INVESTMENT_GOAL_WIDGET("monthly_investment_goal_widget"), SETUP_MONTHLY_TARGET_WIDGET("setup_monthly_target_widget"), + FTUE_WITH_TRACKER_WIDGET("ftue_with_tracker_widget"), } diff --git a/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/InvestmentGenericComposableWidgetFactory.kt b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/InvestmentGenericComposableWidgetFactory.kt index 2357b1e5d2..9590f1b1cb 100644 --- a/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/InvestmentGenericComposableWidgetFactory.kt +++ b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/InvestmentGenericComposableWidgetFactory.kt @@ -18,6 +18,8 @@ import com.navi.amc.common.composables.widgets.SpacerWidgetComposable import com.navi.amc.common.model.GenericComposableWidget import com.navi.amc.common.model.widgets.SpacerWidget import com.navi.amc.fundbuy.composables.widgets.SetupMonthlyTargetWidgetComposable +import com.navi.amc.fundbuy.models.widgets.FtueWithTrackerWidget +import com.navi.amc.fundbuy.models.widgets.RepeatOrderWidget import com.navi.amc.fundbuy.models.widgets.RiskFreeFundWidget import com.navi.amc.fundbuy.models.widgets.SetupMonthlyTargetWidget import com.navi.base.model.ActionData @@ -33,7 +35,6 @@ import com.naviapp.home.dashboard.models.investmentTabWidgetData.HighestReturnFu import com.naviapp.home.dashboard.models.investmentTabWidgetData.MonthlyInvestmentGoalWidget import com.naviapp.home.dashboard.models.investmentTabWidgetData.OrdersInProgressWidget import com.naviapp.home.dashboard.models.investmentTabWidgetData.PortfolioWidget -import com.naviapp.home.dashboard.models.investmentTabWidgetData.RepeatOrderWidget import com.naviapp.home.dashboard.models.investmentTabWidgetData.RewardNudgeWidget import com.naviapp.home.dashboard.models.investmentTabWidgetData.SipAutoPayNudgeWidget import com.naviapp.home.dashboard.models.investmentTabWidgetData.TopInvestingFundsWidget @@ -44,6 +45,7 @@ import com.naviapp.home.dashboard.ui.compose.investmentTab.widgets.BannerWithAct import com.naviapp.home.dashboard.ui.compose.investmentTab.widgets.CutOffSellTimerComposable import com.naviapp.home.dashboard.ui.compose.investmentTab.widgets.CutOffTimerWidgetComposable import com.naviapp.home.dashboard.ui.compose.investmentTab.widgets.ExploreMoreWidgetComposable +import com.naviapp.home.dashboard.ui.compose.investmentTab.widgets.FtueWithTrackerWidgetComposable import com.naviapp.home.dashboard.ui.compose.investmentTab.widgets.FundCategoriesWidgetComposable import com.naviapp.home.dashboard.ui.compose.investmentTab.widgets.MonthlyInvestmentGoalWidgetComposable import com.naviapp.home.dashboard.ui.compose.investmentTab.widgets.OrdersInProgressWidgetComposable @@ -219,5 +221,14 @@ fun InvestmentGenericComposableWidgetFactory( ) } } + InvestmentTabWidgetType.FTUE_WITH_TRACKER_WIDGET.value -> { + VisibilityTracker(widgetData = data, onVisible = onVisible) { + FtueWithTrackerWidgetComposable( + widget = data as FtueWithTrackerWidget, + onClick = onClick, + onHopperStart = onHopperStart, + ) + } + } } } 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 742a79a5fe..104b6fa3a6 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,9 +11,11 @@ import BottomSheetData import android.app.Activity import android.os.Bundle import androidx.compose.runtime.MutableState +import com.navi.amc.common.activity.CheckerActivity import com.navi.amc.common.model.AdditionalDataAsyncResponse import com.navi.amc.common.model.NextCtaResponse import com.navi.amc.navigator.NaviAmcDeeplinkNavigator +import com.navi.amc.navigator.NaviAmcDeeplinkNavigator.FTUE import com.navi.amc.utils.AmcAnalytics import com.navi.amc.utils.Constant import com.navi.amc.utils.Constant.CAPS_DATA @@ -81,6 +83,26 @@ class InvestmentsScreenHelper { fun handlePennyDropSuccessCase(ctaData: CtaData) { if ( + (ctaData.url + ?.contains( + NaviAmcDeeplinkNavigator.AMC.plus("/").plus(NaviAmcDeeplinkNavigator.KYC), + true, + ) + .orFalse() || + ctaData.url?.contains(CheckerActivity.HPC_PAN_REDIRECTION_PAGE).orFalse() || + ctaData.url?.contains(CheckerActivity.HPC_NAME_REDIRECTION_PAGE).orFalse()) && + ctaData.parameters + ?.firstOrNull { it.key == Constant.SOURCE } + ?.value + ?.equals(FTUE) == true + ) { + val sourceParam = + hashMapOf().apply { + ctaData.parameters?.forEach { put(it.key.toString(), it.value.orEmpty()) } + put(Constant.KYC_SOURCE_SCREEN, FTUE) + } + TempStorageHelper.kycSourceInfo = sourceParam + } else if ( ctaData ?.url ?.contains( diff --git a/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/VisibilityTracker.kt b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/VisibilityTracker.kt index 45f957db37..a3a742ea69 100644 --- a/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/VisibilityTracker.kt +++ b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/VisibilityTracker.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import com.navi.amc.fundbuy.models.widgets.FundCardData +import com.navi.amc.fundbuy.models.widgets.RepeatOrderWidget import com.navi.amc.fundbuy.models.widgets.RiskFreeFundWidget import com.navi.amc.fundbuy.models.widgets.SetupMonthlyTargetWidget import com.navi.base.model.GenericAnalytics @@ -21,7 +22,6 @@ import com.naviapp.home.dashboard.models.investmentTabWidgetData.MonthlyInvestme import com.naviapp.home.dashboard.models.investmentTabWidgetData.OrderStatusCardData import com.naviapp.home.dashboard.models.investmentTabWidgetData.OrdersInProgressWidget import com.naviapp.home.dashboard.models.investmentTabWidgetData.PortfolioWidget -import com.naviapp.home.dashboard.models.investmentTabWidgetData.RepeatOrderWidget import com.naviapp.home.dashboard.models.investmentTabWidgetData.SipAutoPayNudgeWidget import com.naviapp.home.dashboard.models.investmentTabWidgetData.TopInvestingFundsWidget import com.naviapp.home.dashboard.models.investmentTabWidgetData.WhyInvestWidget diff --git a/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/genericComposables/FtueTrackerCardComposable.kt b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/genericComposables/FtueTrackerCardComposable.kt new file mode 100644 index 0000000000..a3090b19ee --- /dev/null +++ b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/genericComposables/FtueTrackerCardComposable.kt @@ -0,0 +1,178 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.naviapp.home.dashboard.ui.compose.investmentTab.genericComposables + +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.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +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.layout.wrapContentHeight +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.navi.amc.fundbuy.composables.widgets.CustomDashedDivider +import com.navi.amc.fundbuy.models.widgets.FtueWithTrackerActionCardData +import com.navi.base.model.ActionData +import com.navi.base.model.CtaData +import com.navi.base.utils.orElse +import com.navi.common.utils.Constants.HOPPER +import com.navi.design.utils.clickableWithNoGesture +import com.navi.naviwidgets.extensions.NaviImage +import com.navi.naviwidgets.extensions.NaviTextWidgetized +import com.navi.naviwidgets.extensions.hexToColor +import com.navi.naviwidgets.models.FooterButtonState +import com.navi.uitron.model.ui.ComposePadding +import com.navi.uitron.utils.setBackground +import com.navi.uitron.utils.setBorderStroke +import com.navi.uitron.utils.setPadding +import com.naviapp.home.dashboard.ui.compose.investmentTab.InvestmentsScreenHelper + +@Composable +fun FtueTrackerCardComposable( + data: FtueWithTrackerActionCardData, + onClick: (actionData: ActionData?) -> Unit, + onHopperStart: (ctaData: CtaData, buttonState: MutableState) -> Unit = + { _, _ -> + }, +) { + Column( + modifier = + Modifier.shadow( + elevation = (data.actionCardProperty?.cardProperty?.elevation ?: 16).dp, + spotColor = (hexToColor(data.actionCardProperty?.cardProperty?.spotColor)), + ambientColor = (hexToColor(data.actionCardProperty?.cardProperty?.ambientColor)), + ) + .setBorderStroke(data.actionCardProperty?.cardProperty?.borderStrokeData) + .setBackground( + brushData = data.actionCardProperty?.cardProperty?.backGroundBrushData, + uiTronShape = data.actionCardProperty?.cardProperty?.shape, + backgroundColor = data.actionCardProperty?.cardProperty?.backgroundColor, + ) + .setPadding(data.actionCardProperty?.cardProperty?.padding) + .wrapContentHeight() + .fillMaxWidth() + ) { + data.trackerItems?.let { items -> + Row(modifier = Modifier.fillMaxWidth()) { + items.forEachIndexed { index, iconWithTextCard -> + Column( + modifier = + Modifier.fillMaxHeight() + .width( + (data.actionCardProperty + ?.trackerItemsProperty + ?.width + ?.toInt() + ?.orElse(65) ?: 65) + .dp + ), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + NaviImage(imageFieldData = iconWithTextCard.icon) + Spacer( + modifier = + Modifier.fillMaxWidth() + .height( + (data.actionCardProperty?.trackerItemsProperty?.margin?.top + ?: 4) + .dp + ) + ) + NaviTextWidgetized(textFieldData = iconWithTextCard.text) + } + + if (index != items.size - 1) { + Box( + modifier = + Modifier.fillMaxSize() + .weight(1f) + .setPadding( + data.actionCardProperty?.trackerDividerProperty?.padding + ?: ComposePadding( + start = 8, + end = 8, + top = 20, + bottom = 0, + ) + ), + contentAlignment = Alignment.CenterStart, + ) { + CustomDashedDivider( + modifier = Modifier.padding(top = 0.dp, bottom = 0.dp), + color = Color.Black, + thickness = 1.dp, + ) + } + } + } + } + } + data.buttonText?.let { + Column( + modifier = + Modifier.fillMaxWidth() + .setPadding(data.actionCardProperty?.buttonProperty?.padding) + .setBackground( + brushData = + data.actionCardProperty?.buttonProperty?.backGroundBrushData, + uiTronShape = data.actionCardProperty?.buttonProperty?.shape, + backgroundColor = + data.actionCardProperty?.buttonProperty?.backgroundColor, + ) + .clickableWithNoGesture { + data.actionData?.let { actionData -> + if (actionData.url == HOPPER) { + InvestmentsScreenHelper() + .setActionStatus( + actionData = actionData, + buttonState = + mutableStateOf( + FooterButtonState.ENABLED + ), + onClick = onClick, + onHopperStart = onHopperStart, + ) + } else { + onClick(actionData) + } + } + }, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + NaviTextWidgetized( + textFieldData = it, + modifier = + Modifier.setPadding(data.actionCardProperty?.buttonTextProperty?.padding), + ) + } + } + data.footerTexts?.let { + Row( + modifier = + Modifier.fillMaxWidth() + .setPadding(data.actionCardProperty?.footerTextsProperty?.padding), + horizontalArrangement = Arrangement.Center, + ) { + NaviTextWidgetized(textFieldData = it.leftText) + NaviTextWidgetized(textFieldData = it.rightText) + } + } + } +} diff --git a/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/widgets/ExploreMoreWidgetComposable.kt b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/widgets/ExploreMoreWidgetComposable.kt index 5fb4de2cfa..ade0a28c8c 100644 --- a/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/widgets/ExploreMoreWidgetComposable.kt +++ b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/widgets/ExploreMoreWidgetComposable.kt @@ -109,8 +109,16 @@ fun ExploreMoreCardComposable( ) Spacer( modifier = - Modifier.weight( - cardData.property?.spacingWeight?.start ?: DEFAULT_CARD_WEIGHT + Modifier.then( + cardData.titleProperty?.padding?.start?.let { startPadding -> + Modifier.width(startPadding.dp) + } + ?: run { + Modifier.weight( + cardData.property?.spacingWeight?.start + ?: DEFAULT_CARD_WEIGHT + ) + } ) ) NaviTextWidgetized(textFieldData = cardData.title) diff --git a/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/widgets/FtueWithTrackerWidgetComposable.kt b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/widgets/FtueWithTrackerWidgetComposable.kt new file mode 100644 index 0000000000..eda7e48f98 --- /dev/null +++ b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/widgets/FtueWithTrackerWidgetComposable.kt @@ -0,0 +1,213 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.naviapp.home.dashboard.ui.compose.investmentTab.widgets + +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +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.layout.wrapContentHeight +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.unit.dp +import com.navi.amc.fundbuy.models.widgets.FtueWithTrackerWidget +import com.navi.base.model.ActionData +import com.navi.base.model.CtaData +import com.navi.common.ui.compose.RolodexAnimationComposableV2 +import com.navi.naviwidgets.extensions.NaviImage +import com.navi.naviwidgets.extensions.NaviTextWidgetized +import com.navi.naviwidgets.models.FooterButtonState +import com.navi.uitron.model.ui.BrushData +import com.navi.uitron.model.ui.BrushType +import com.navi.uitron.model.ui.ColorStop +import com.navi.uitron.model.ui.ComposePadding +import com.navi.uitron.utils.setBackground +import com.navi.uitron.utils.setPadding +import com.naviapp.home.dashboard.ui.compose.investmentTab.genericComposables.FtueTrackerCardComposable + +@Composable +fun FtueWithTrackerWidgetComposable( + widget: FtueWithTrackerWidget, + onClick: (actionData: ActionData?) -> Unit, + onHopperStart: (ctaData: CtaData, buttonState: MutableState) -> Unit = + { _, _ -> + }, +) { + widget.widgetData?.content?.let { content -> + Box { + val illustrationHeight = + (LocalConfiguration.current.screenWidthDp) * + (content.properties?.cardProperty?.heightFactor ?: 0.65f) + + Box( + modifier = + Modifier.height(illustrationHeight.dp) + .width(LocalConfiguration.current.screenWidthDp.dp) + .setBackground( + brushData = + content.properties?.cardProperty?.backGroundBrushData + ?: BrushData( + brushType = BrushType.LINEAR.name, + colorStops = + listOf( + ColorStop(0.0f, "#FFFFFF"), + ColorStop(1f, "#FFFBD6"), + ), + ), + uiTronShape = content.properties?.cardProperty?.shape, + backgroundColor = content.properties?.cardProperty?.backgroundColor, + ) + ) { + Column(modifier = Modifier.fillMaxWidth()) { + Column(horizontalAlignment = Alignment.Start) { + content.topLogo?.let { + NaviImage( + imageFieldData = it, + modifier = + Modifier.setPadding( + content.properties?.topLogoProperty?.padding + ?: ComposePadding(0, 0, 14, 0) + ) + .align(Alignment.CenterHorizontally), + ) + } + content.topText?.let { + NaviTextWidgetized( + textFieldData = it, + modifier = + Modifier.padding( + start = + (content.properties?.topTextProperty?.padding?.start + ?: 16) + .dp, + top = + (content.properties?.topTextProperty?.padding?.top + ?: 30) + .dp, + ), + ) + } + content.rolodexItems?.let { items -> + if (items.isNotEmpty()) { + Row( + modifier = + Modifier.setPadding( + content.properties?.rolodexItemProperty?.padding + ?: ComposePadding(0, 0, 6, 0) + ), + horizontalArrangement = Arrangement.Start, + ) { + RolodexAnimationComposableV2( + composableList = + List(items.size ?: 0) { + { NaviTextWidgetized(textFieldData = items[it]) } + }, + delayInMillis = + content.properties + ?.rolodexItemProperty + ?.animationData + ?.delayInMillis ?: 2200, + enterAnimation = slideInVertically { it }, + exitAnimation = slideOutVertically { -it }, + contentAlignment = Alignment.CenterStart, + ) + } + } + } + content.chipItems?.let { chipItems -> + if (chipItems.isNotEmpty()) { + Row( + modifier = + Modifier.fillMaxWidth() + .wrapContentHeight() + .setPadding( + content.properties?.chipProperty?.padding + ?: ComposePadding(16, 0, 12, 0) + ) + ) { + repeat(chipItems.size) { + Column( + modifier = + Modifier.setBackground( + backgroundColor = + content.properties + ?.chipItemProperty + ?.backgroundColor, + brushData = + content.properties + ?.chipItemProperty + ?.backGroundBrushData, + uiTronShape = + content.properties?.chipItemProperty?.shape, + ) + ) { + NaviTextWidgetized( + modifier = + Modifier.setPadding( + content.properties + ?.chipItemProperty + ?.padding ?: ComposePadding(8, 8, 4, 4) + ), + textFieldData = chipItems[it], + ) + } + Spacer( + modifier = + Modifier.width( + (content.properties?.chipProperty?.padding?.end + ?: 10) + .dp + ) + ) + } + } + } + } + } + } + } + + content.actionCardData?.let { actionCardData -> + Box( + modifier = + Modifier.padding( + top = + (illustrationHeight * + (actionCardData.actionCardProperty + ?.cardProperty + ?.heightFactor ?: 0.65f)) + .dp, + start = + (actionCardData.actionCardProperty?.cardProperty?.margin?.start + ?: 16) + .dp, + end = + (actionCardData.actionCardProperty?.cardProperty?.margin?.end ?: 16) + .dp, + ) + ) { + FtueTrackerCardComposable( + data = actionCardData, + onClick = onClick, + onHopperStart = onHopperStart, + ) + } + } + } + } +} diff --git a/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/widgets/RepeatOrderWidgetComposable.kt b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/widgets/RepeatOrderWidgetComposable.kt index 816a5ff2b9..e97b41f8c4 100644 --- a/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/widgets/RepeatOrderWidgetComposable.kt +++ b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/investmentTab/widgets/RepeatOrderWidgetComposable.kt @@ -10,11 +10,11 @@ package com.naviapp.home.dashboard.ui.compose.investmentTab.widgets import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import com.navi.amc.fundbuy.models.widgets.RepeatOrderWidget import com.navi.base.model.ActionData import com.navi.base.model.GenericAnalytics import com.navi.naviwidgets.extensions.NaviTextWidgetized import com.navi.uitron.utils.setPadding -import com.naviapp.home.dashboard.models.investmentTabWidgetData.RepeatOrderWidget import com.naviapp.home.dashboard.ui.compose.investmentTab.genericComposables.CardListComposable import com.naviapp.home.dashboard.viewmodels.InvestmentsVm import com.naviapp.utils.Constants.DEFAULT_CARD_WIDTH_FACTOR diff --git a/android/navi-amc/build.gradle b/android/navi-amc/build.gradle index de9746af5c..4876c1bfc7 100644 --- a/android/navi-amc/build.gradle +++ b/android/navi-amc/build.gradle @@ -74,6 +74,7 @@ dependencies { implementation libs.digio.gateway.kyc implementation libs.digitap implementation libs.philjay.mpAndroidChart + implementation libs.raamcosta.composeDestinations.animation.core androidTestImplementation libs.androidx.test.espresso.core androidTestImplementation libs.androidx.test.junit @@ -82,4 +83,5 @@ dependencies { ksp libs.androidx.hilt.compiler ksp libs.dagger.hiltCompiler + ksp libs.raamcosta.composeDestinations.ksp } diff --git a/android/navi-amc/src/main/AndroidManifest.xml b/android/navi-amc/src/main/AndroidManifest.xml index 189e3a3bc6..7a521fbc80 100644 --- a/android/navi-amc/src/main/AndroidManifest.xml +++ b/android/navi-amc/src/main/AndroidManifest.xml @@ -44,6 +44,13 @@ android:theme="@style/BaseThemeStyle" android:launchMode="singleTop" android:windowSoftInputMode="adjustNothing" /> + + \ No newline at end of file diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/composables/AmcCardListComposable.kt b/android/navi-amc/src/main/java/com/navi/amc/common/composables/AmcCardListComposable.kt index 31bf84ce3f..bf6af315ec 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/common/composables/AmcCardListComposable.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/common/composables/AmcCardListComposable.kt @@ -7,23 +7,28 @@ package com.navi.amc.common.composables +import PaymentCard import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.PageSize +import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.navi.amc.fundbuy.composables.cards.MonthlyInvestmentGoalCardComposable +import com.navi.amc.fundbuy.composables.cards.PaymentCardComposable import com.navi.amc.fundbuy.models.cards.InvestmentGoalCardData import com.navi.amc.utils.Constant.FREE_SCROLL import com.navi.amc.utils.Constant.FUND_CARD_DEFAULT_COLUMN_WEIGHT import com.navi.amc.utils.Constant.MONTHLY_INVESTMENT_GOAL_CARD import com.navi.amc.utils.Constant.RANK_OF_CARD +import com.navi.amc.utils.Constant.REPEAT_ORDER import com.navi.base.model.ActionData @Composable @@ -36,6 +41,8 @@ fun AmcCardListComposable( listHorizontalPadding: Dp = 16.dp, spacingBetweenCard: Dp = 16.dp, scrollType: String, + listState: LazyListState? = null, + pagerState: PagerState? = null, ) { val configuration = LocalConfiguration.current val screenWidth = configuration.screenWidthDp.dp @@ -44,6 +51,7 @@ fun AmcCardListComposable( when (scrollType) { FREE_SCROLL -> { LazyRow( + state = listState ?: LazyListState(), contentPadding = PaddingValues(horizontal = listHorizontalPadding), horizontalArrangement = Arrangement.spacedBy(spacingBetweenCard), ) { @@ -67,9 +75,8 @@ fun AmcCardListComposable( } } else -> { - val pagerState = rememberPagerState(pageCount = { cardList.size }) HorizontalPager( - state = pagerState, + state = pagerState ?: rememberPagerState(pageCount = { cardList.size }), pageSize = PageSize.Fixed(cardWidth), contentPadding = PaddingValues(horizontal = listHorizontalPadding), pageSpacing = spacingBetweenCard, @@ -111,5 +118,13 @@ fun RenderCardBasedOnType( onClick = onClick, ) } + REPEAT_ORDER -> { + PaymentCardComposable( + paymentCard = data as PaymentCard, + cardWidth = cardWidth, + onClick = onClick, + cardType = "NORMAL_CARD", + ) + } } } diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/composables/AmcCommonWidgetListRenderer.kt b/android/navi-amc/src/main/java/com/navi/amc/common/composables/AmcCommonWidgetListRenderer.kt index e053c7fd2b..a970418e95 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/common/composables/AmcCommonWidgetListRenderer.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/common/composables/AmcCommonWidgetListRenderer.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier import com.navi.amc.common.model.GenericComposableWidget import com.navi.amc.fundbuy.models.AmcHeaderExtraData +import com.navi.amc.fundbuy.models.SelectionData import com.navi.amc.utils.Constant.WHITE import com.navi.base.model.ActionData import kotlinx.coroutines.delay @@ -34,6 +35,7 @@ fun AmcCommonWidgetListRenderer( scrollToIndex: Int? = null, extraData: AmcHeaderExtraData? = null, eventHandler: (actionData: ActionData?) -> Unit, + selectionExtraData: SelectionData? = null, ) { val scrollState = rememberLazyListState() @@ -88,6 +90,7 @@ fun AmcCommonWidgetListRenderer( val widget = widgetList?.get(index) AmcComposableWidgetFactory( data = widget, + selectionData = selectionExtraData, onClick = { actionData -> actionListener(actionData) }, onVisible = { actionData -> eventHandler(actionData) }, ) diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/composables/AmcComposableWidgetFactory.kt b/android/navi-amc/src/main/java/com/navi/amc/common/composables/AmcComposableWidgetFactory.kt index 2dbbea8286..6a972f1bc9 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/common/composables/AmcComposableWidgetFactory.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/common/composables/AmcComposableWidgetFactory.kt @@ -13,26 +13,34 @@ import com.navi.amc.common.composables.widgets.SpacerWidgetComposable import com.navi.amc.common.model.AmcWidgetType import com.navi.amc.common.model.GenericComposableWidget import com.navi.amc.common.model.widgets.SpacerWidget -import com.navi.amc.fundbuy.composables.BannerWidgetComposable -import com.navi.amc.fundbuy.composables.FooterWidgetComposable -import com.navi.amc.fundbuy.composables.HelpInfoWidgetComposable -import com.navi.amc.fundbuy.composables.OnTimeAssuranceWidgetComposable +import com.navi.amc.fundbuy.composables.widgets.BannerWidgetComposable +import com.navi.amc.fundbuy.composables.widgets.BannerWithLottieWidgetComposable +import com.navi.amc.fundbuy.composables.widgets.FooterWidgetComposable +import com.navi.amc.fundbuy.composables.widgets.HelpInfoWidgetComposable import com.navi.amc.fundbuy.composables.widgets.InvestmentDetailsWidgetComposable import com.navi.amc.fundbuy.composables.widgets.InvestmentTrackerWidgetComposable import com.navi.amc.fundbuy.composables.widgets.MonthlyInvestmentGoalWidgetComposable +import com.navi.amc.fundbuy.composables.widgets.OnTimeAssuranceWidgetComposable +import com.navi.amc.fundbuy.composables.widgets.RepeatOrderWidgetComposable import com.navi.amc.fundbuy.composables.widgets.SetupMonthlyTargetWidgetComposable +import com.navi.amc.fundbuy.composables.widgets.TitleContentRadioWidgetComposable +import com.navi.amc.fundbuy.models.SelectionData import com.navi.amc.fundbuy.models.widgets.InvestmentDetailsWidget import com.navi.amc.fundbuy.models.widgets.MonthlyInvestmentGoalWidget import com.navi.amc.fundbuy.models.widgets.MonthlyInvestmentTrackerWidget import com.navi.amc.fundbuy.models.widgets.OnTimeAssuranceInfoWidget +import com.navi.amc.fundbuy.models.widgets.RepeatOrderWidget import com.navi.amc.fundbuy.models.widgets.RiskFreeFundWidget import com.navi.amc.fundbuy.models.widgets.SetupMonthlyTargetWidget +import com.navi.amc.fundbuy.models.widgets.TitleContentRadioWidget import com.navi.amc.fundbuy.models.widgets.TitleSubtitleImageWidget +import com.navi.amc.fundbuy.models.widgets.TitleSubtitleLottieWidget import com.navi.base.model.ActionData @Composable fun AmcComposableWidgetFactory( data: GenericComposableWidget? = null, + selectionData: SelectionData? = null, onClick: (actionData: ActionData?) -> Unit, onVisible: (actionData: ActionData?) -> Unit, ) { @@ -74,6 +82,9 @@ fun AmcComposableWidgetFactory( onVisible = onVisible, ) } + AmcWidgetType.REPEAT_ORDER_WIDGET.value -> { + RepeatOrderWidgetComposable(widget = data as RepeatOrderWidget, onClick = onClick) + } AmcWidgetType.FOOTER_WIDGET.value -> { FooterWidgetComposable(widget = data as TitleSubtitleImageWidget) } @@ -83,5 +94,15 @@ fun AmcComposableWidgetFactory( AmcWidgetType.BANNER_WIDGET.value -> { BannerWidgetComposable(widget = data as TitleSubtitleImageWidget) } + AmcWidgetType.BANNER_WITH_LOTTIE_WIDGET.value -> { + BannerWithLottieWidgetComposable(widget = data as TitleSubtitleLottieWidget) + } + AmcWidgetType.TITLE_CONTENT_RADIO_WIDGET.value -> { + TitleContentRadioWidgetComposable( + widget = data as TitleContentRadioWidget, + onClick = onClick, + selectionData = selectionData, + ) + } } } diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/composables/widgets/SpacerWidgetComposable.kt b/android/navi-amc/src/main/java/com/navi/amc/common/composables/widgets/SpacerWidgetComposable.kt index 9240208079..9e77abe8f6 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/common/composables/widgets/SpacerWidgetComposable.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/common/composables/widgets/SpacerWidgetComposable.kt @@ -7,17 +7,25 @@ package com.navi.amc.common.composables.widgets +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.navi.amc.common.model.widgets.SpacerWidget import com.navi.amc.utils.Constant.SPACER_DEFAULT +import com.navi.naviwidgets.extensions.hexToColor @Composable fun SpacerWidgetComposable(spacerData: SpacerWidget) { spacerData.widgetData?.let { data -> - Spacer(modifier = Modifier.height((data.height ?: SPACER_DEFAULT).dp)) + Spacer( + modifier = + Modifier.fillMaxWidth() + .height((data.height ?: SPACER_DEFAULT).dp) + .background(color = hexToColor(data.backgroundColor)) + ) } } diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/model/AmcWidgetJsonDeserializer.kt b/android/navi-amc/src/main/java/com/navi/amc/common/model/AmcWidgetJsonDeserializer.kt index b0f63d8c87..97bc1a1683 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/common/model/AmcWidgetJsonDeserializer.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/common/model/AmcWidgetJsonDeserializer.kt @@ -15,9 +15,12 @@ import com.navi.amc.fundbuy.models.widgets.InvestmentDetailsWidget import com.navi.amc.fundbuy.models.widgets.MonthlyInvestmentGoalWidget import com.navi.amc.fundbuy.models.widgets.MonthlyInvestmentTrackerWidget import com.navi.amc.fundbuy.models.widgets.OnTimeAssuranceInfoWidget +import com.navi.amc.fundbuy.models.widgets.RepeatOrderWidget import com.navi.amc.fundbuy.models.widgets.RiskFreeFundWidget import com.navi.amc.fundbuy.models.widgets.SetupMonthlyTargetWidget +import com.navi.amc.fundbuy.models.widgets.TitleContentRadioWidget import com.navi.amc.fundbuy.models.widgets.TitleSubtitleImageWidget +import com.navi.amc.fundbuy.models.widgets.TitleSubtitleLottieWidget import java.lang.reflect.Type class AmcWidgetJsonDeserializer : JsonDeserializer { @@ -45,6 +48,11 @@ class AmcWidgetJsonDeserializer : JsonDeserializer { AmcWidgetType.FOOTER_WIDGET.value, AmcWidgetType.BANNER_WIDGET.value, AmcWidgetType.HELP_INFO_WIDGET.value -> TitleSubtitleImageWidget::class.java + AmcWidgetType.REPEAT_ORDER_WIDGET.value -> RepeatOrderWidget::class.java + AmcWidgetType.BANNER_WITH_LOTTIE_WIDGET.value -> + TitleSubtitleLottieWidget::class.java + AmcWidgetType.TITLE_CONTENT_RADIO_WIDGET.value -> + TitleContentRadioWidget::class.java else -> null } return if (className != null) { diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/model/AmcWidgetType.kt b/android/navi-amc/src/main/java/com/navi/amc/common/model/AmcWidgetType.kt index 2781332901..490838aa6e 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/common/model/AmcWidgetType.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/common/model/AmcWidgetType.kt @@ -18,4 +18,7 @@ enum class AmcWidgetType(val value: String) { HELP_INFO_WIDGET("help_info_widget"), BANNER_WIDGET("banner_widget"), FOOTER_WIDGET("footer_widget"), + REPEAT_ORDER_WIDGET("repeat_order_widget"), + BANNER_WITH_LOTTIE_WIDGET("banner_with_lottie_widget"), + TITLE_CONTENT_RADIO_WIDGET("fund_investing_type"), } diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/model/Footer.kt b/android/navi-amc/src/main/java/com/navi/amc/common/model/Footer.kt index bdd8c8b087..7143561e72 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/common/model/Footer.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/common/model/Footer.kt @@ -10,6 +10,7 @@ package com.navi.amc.common.model import android.os.Parcelable import com.google.gson.annotations.SerializedName 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.DataSafeWidget import kotlinx.parcelize.Parcelize @@ -24,6 +25,8 @@ data class Footer( @SerializedName("note") var note: DataSafeWidget? = null, @SerializedName("footerCallout") var footerCallout: FooterCallout? = null, @SerializedName("footerCalloutList") var footerCalloutList: FooterCalloutList? = null, + @SerializedName("footerProperties") var footerProperties: FooterProperties? = null, + @SerializedName("showShadow") var showShadow: Boolean? = null, ) : Parcelable @Parcelize @@ -47,3 +50,9 @@ class TimerTextList( @SerializedName("atTimerInfo") var atTimerInfo: TextWithStyle? = null, @SerializedName("afterTimerInfo") var afterTimerInfo: TextWithStyle? = null, ) : Parcelable + +@Parcelize +data class FooterProperties( + @SerializedName("nextCtaProperty") val nextCtaProperty: InvestmentBaseProperty? = null, + @SerializedName("backCtaProperty") val backCtaProperty: InvestmentBaseProperty? = null, +) : Parcelable diff --git a/android/navi-amc/src/main/java/com/navi/amc/common/model/widgets/SpacerWidget.kt b/android/navi-amc/src/main/java/com/navi/amc/common/model/widgets/SpacerWidget.kt index 9baf063b84..daaf68a649 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/common/model/widgets/SpacerWidget.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/common/model/widgets/SpacerWidget.kt @@ -16,4 +16,7 @@ data class SpacerWidget( @SerializedName("widgetData") val widgetData: SpacerWidgetData? = null, ) : GenericComposableWidget -data class SpacerWidgetData(@SerializedName("height") val height: Int? = null) +data class SpacerWidgetData( + @SerializedName("height") val height: Int? = null, + @SerializedName("backgroundColor", alternate = ["bgColor"]) val backgroundColor: String? = null, +) diff --git a/android/navi-amc/src/main/java/com/navi/amc/compose/common/model/AmcGenericScreenResponse.kt b/android/navi-amc/src/main/java/com/navi/amc/compose/common/model/AmcGenericScreenResponse.kt new file mode 100644 index 0000000000..c6fbc64229 --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/compose/common/model/AmcGenericScreenResponse.kt @@ -0,0 +1,19 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.compose.common.model + +import com.google.gson.annotations.SerializedName +import com.navi.amc.common.model.Footer +import com.navi.amc.common.model.GenericComposableWidget +import com.navi.common.model.Header + +data class AmcGenericScreenResponse( + @SerializedName("header") val header: Header? = null, + @SerializedName("content") val content: List? = null, + @SerializedName("footer") val footer: Footer? = null, +) diff --git a/android/navi-amc/src/main/java/com/navi/amc/compose/common/model/ScreenState.kt b/android/navi-amc/src/main/java/com/navi/amc/compose/common/model/ScreenState.kt new file mode 100644 index 0000000000..ae52b1b21f --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/compose/common/model/ScreenState.kt @@ -0,0 +1,20 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.compose.common.model + +import com.navi.common.network.models.GenericErrorResponse + +sealed class ScreenState { + data object Loading : ScreenState() + + data class Success(val amcGenericScreenResponse: AmcGenericScreenResponse) : ScreenState() + + data class Error(val error: GenericErrorResponse? = null) : ScreenState() + + data object Empty : ScreenState() +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/compose/common/ui/AmcRouterScreen.kt b/android/navi-amc/src/main/java/com/navi/amc/compose/common/ui/AmcRouterScreen.kt new file mode 100644 index 0000000000..671d7f77ed --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/compose/common/ui/AmcRouterScreen.kt @@ -0,0 +1,75 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.compose.common.ui + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import com.navi.amc.compose.common.utils.NaviAmcScreen +import com.navi.amc.compose.common.utils.clearBackStackUpToAndNavigate +import com.navi.amc.compose.destinations.FtueEducateScreenDestination +import com.navi.amc.compose.destinations.FtueFundSelectScreenDestination +import com.navi.amc.compose.entry.AmcComposeActivity +import com.navi.amc.navigator.NaviAmcDeeplinkNavigator.FTUE +import com.navi.amc.utils.Constant.SUB_REDIRECT +import com.navi.common.utils.Constants.SECOND_IDENTIFIER +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import kotlinx.coroutines.launch + +@Destination +@Composable +fun AmcRouterScreen(amcComposeActivity: AmcComposeActivity, navigator: DestinationsNavigator) { + val scope = rememberCoroutineScope() + LaunchedEffect(Unit) { + val moduleName = amcComposeActivity.intent.getStringExtra(SECOND_IDENTIFIER).orEmpty() + val screenName = amcComposeActivity.intent.getStringExtra(SUB_REDIRECT).orEmpty() + + val destination = + when (moduleName) { + FTUE -> { + when (screenName) { + NaviAmcScreen.AMC_FTUE_EDUCATE_SCREEN.screenName -> + FtueEducateScreenDestination + NaviAmcScreen.AMC_FTUE_FUND_SELECT_SCREEN.screenName -> + FtueFundSelectScreenDestination + else -> { + null + } + } + } + else -> { + null + } + } + + scope.launch { + destination?.let { + navigator.clearBackStackUpToAndNavigate( + destination = destination, + inclusive = true, + popUpTo = FtueEducateScreenDestination, + ) + } ?: run { amcComposeActivity.finish() } + } + } + + Scaffold( + modifier = Modifier.fillMaxSize().imePadding(), + topBar = {}, + content = { innerPadding -> + Column(modifier = Modifier.fillMaxSize().padding(innerPadding)) { AmcShimmer() } + }, + ) +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/compose/common/ui/AmcShimmer.kt b/android/navi-amc/src/main/java/com/navi/amc/compose/common/ui/AmcShimmer.kt new file mode 100644 index 0000000000..39ba5f55a5 --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/compose/common/ui/AmcShimmer.kt @@ -0,0 +1,51 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.compose.common.ui + +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.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun AmcShimmer() { + Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp)) { + Spacer(modifier = Modifier.height(24.dp)) + Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { + Box(modifier = Modifier.height(34.dp).width(94.dp).shimmerEffect()) + Box(modifier = Modifier.height(34.dp).width(148.dp).shimmerEffect()) + } + Spacer(modifier = Modifier.height(24.dp)) + Box(modifier = Modifier.height(108.dp).fillMaxWidth().shimmerEffect()) + Spacer(modifier = Modifier.height(32.dp)) + Box(modifier = Modifier.height(24.dp).width(176.dp).shimmerEffect()) + Spacer(modifier = Modifier.height(24.dp)) + Box(modifier = Modifier.height(132.dp).fillMaxWidth().shimmerEffect()) + + Spacer(modifier = Modifier.height(32.dp)) + Box(modifier = Modifier.height(24.dp).width(120.dp).shimmerEffect()) + + Spacer(modifier = Modifier.height(24.dp)) + Box(modifier = Modifier.height(104.dp).fillMaxWidth().shimmerEffect()) + + Spacer(modifier = Modifier.height(24.dp)) + Box(modifier = Modifier.height(78.dp).fillMaxWidth().shimmerEffect()) + Spacer(modifier = Modifier.height(32.dp)) + Box(modifier = Modifier.height(24.dp).width(176.dp).shimmerEffect()) + Spacer(modifier = Modifier.height(24.dp)) + Box(modifier = Modifier.height(132.dp).fillMaxWidth().shimmerEffect()) + } +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/compose/common/ui/Ext.kt b/android/navi-amc/src/main/java/com/navi/amc/compose/common/ui/Ext.kt new file mode 100644 index 0000000000..eed696adff --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/compose/common/ui/Ext.kt @@ -0,0 +1,151 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.compose.common.ui + +import android.app.Activity +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ModalBottomSheetDefaults +import androidx.compose.material.ModalBottomSheetLayout +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.ripple +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.compose.LocalLifecycleOwner +import com.navi.amc.utils.AmcColor +import com.navi.common.utils.ClickDebounce +import com.navi.common.utils.get +import com.navi.naviwidgets.extensions.hexToInt + +@Composable +fun AmcModalBottomSheetLayout( + sheetContent: @Composable ColumnScope.() -> Unit, + modifier: Modifier = Modifier, + sheetState: ModalBottomSheetState, + sheetShape: Shape = RoundedCornerShape(topStart = 8.dp, topEnd = 8.dp), + sheetElevation: Dp = ModalBottomSheetDefaults.Elevation, + scrimColor: Color = ModalBottomSheetDefaults.scrimColor, + content: @Composable () -> Unit, +) { + Column(modifier = Modifier.navigationBarsPadding()) { + ModalBottomSheetLayout( + sheetContent = sheetContent, + modifier = modifier, + sheetState = sheetState, + sheetShape = sheetShape, + sheetElevation = sheetElevation, + scrimColor = scrimColor, + content = content, + ) + } +} + +fun Modifier.clickableDebounce( + debounceTime: Long = 300L, + showRipple: Boolean = true, + rippleColor: Color = Color.Black.copy(alpha = 0.3f), + onClick: () -> Unit, +) = composed { + val clickDebounce = remember { ClickDebounce.get() } + + Modifier.clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = if (showRipple) ripple(color = rippleColor) else null, + onClick = { clickDebounce.processClick(onClick = onClick, debounceTime = debounceTime) }, + ) +} + +fun Modifier.shimmerEffect(shimmerColor: List? = null): Modifier = composed { + var size by remember { mutableFloatStateOf(0f) } + val transition = rememberInfiniteTransition(label = "") + val startOffsetX by + transition.animateFloat( + initialValue = -2 * size, + targetValue = 2 * size, + animationSpec = + infiniteRepeatable(animation = tween(durationMillis = 1000, easing = LinearEasing)), + label = "", + ) + + drawBehind { + size = this.size.width + drawRect( + size = this.size, + brush = + Brush.linearGradient( + colors = + shimmerColor + ?: listOf(Color(0xFFF9F9FB), Color(0xFFE9E7F0), Color(0xFFF9F9FB)), + start = Offset(startOffsetX, 0f), + end = Offset(startOffsetX + size, this.size.height), + ), + ) + } +} + +@Composable +fun ShadowStrip( + modifier: Modifier = Modifier, + height: Dp = 16.dp, + brush: Brush = + Brush.verticalGradient( + colors = listOf(AmcColor.bgDefaultWhite, Color(0xFFCCCCCC).copy(alpha = 0.3f)), + startY = 0f, + endY = 100f, + ), +) { + Box(modifier = modifier.height(height = height).background(brush = brush)) +} + +@Composable +fun SetStatusBarColor(activity: Activity, colorResId: Int = hexToInt("#FFFFFF")) { + val lifecycleOwner = LocalLifecycleOwner.current + + DisposableEffect(key1 = lifecycleOwner) { + val observer = LifecycleEventObserver { _, event -> + when (event) { + Lifecycle.Event.ON_RESUME, + Lifecycle.Event.ON_CREATE -> { + activity.window.statusBarColor = colorResId + } + else -> Unit + } + } + lifecycleOwner.lifecycle.addObserver(observer) + + onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } + } +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/compose/common/ui/NaviAmcFooter.kt b/android/navi-amc/src/main/java/com/navi/amc/compose/common/ui/NaviAmcFooter.kt new file mode 100644 index 0000000000..d82f19734a --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/compose/common/ui/NaviAmcFooter.kt @@ -0,0 +1,105 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.compose.common.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.navi.amc.common.model.Footer +import com.navi.amc.utils.AmcColor +import com.navi.base.model.ActionData +import com.navi.naviwidgets.extensions.NaviTextWidgetized +import com.navi.naviwidgets.extensions.setPadding +import com.navi.naviwidgets.models.response.TextFieldData +import com.navi.uitron.model.ui.ComposePadding +import com.navi.uitron.utils.setBackground +import com.navi.uitron.utils.setPadding + +@Composable +fun NaviAmcFooter( + footer: Footer? = null, + onNextCtaClicked: ((actionData: ActionData?) -> Unit)? = null, + onBackCtaClicked: ((actionData: ActionData?) -> Unit)? = null, +) { + Column(modifier = Modifier.background(color = AmcColor.bgDefaultWhite)) { + if (footer?.showShadow == true) { + ShadowStrip(modifier = Modifier.fillMaxWidth()) + } + Spacer(modifier = Modifier.height(32.dp)) + Row( + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically, + ) { + footer?.backCta?.let { + val backCtaProperty = footer.footerProperties?.backCtaProperty + NaviTextWidgetized( + modifier = + Modifier.clickable { onBackCtaClicked?.invoke(footer.backCta) } + .setBackground( + backgroundColor = backCtaProperty?.backgroundColor, + uiTronShape = backCtaProperty?.shape, + brushData = backCtaProperty?.backGroundBrushData, + ) + .setPadding(backCtaProperty?.padding ?: ComposePadding(16, 16, 12, 12)) + .weight(backCtaProperty?.columnWeight ?: 1f), + textFieldData = + buildFooterTextFieldData( + text = footer.backCta?.title, + textColor = "#1F002A", + ), + ) + Spacer(modifier = Modifier.width(16.dp)) + } + footer?.nextCta?.let { + val nextCtaProperty = footer.footerProperties?.nextCtaProperty + NaviTextWidgetized( + modifier = + Modifier.clickable { onNextCtaClicked?.invoke(footer.nextCta) } + .setBackground( + backgroundColor = nextCtaProperty?.backgroundColor, + uiTronShape = nextCtaProperty?.shape, + brushData = nextCtaProperty?.backGroundBrushData, + ) + .setPadding(nextCtaProperty?.padding ?: ComposePadding(16, 16, 12, 12)) + .weight(nextCtaProperty?.columnWeight ?: 1f), + textFieldData = + buildFooterTextFieldData( + text = footer.nextCta?.title, + textColor = footer.nextCta?.titleColor, + ), + ) + } + } + Spacer(modifier = Modifier.height(32.dp)) + } +} + +fun buildFooterTextFieldData(text: String? = null, textColor: String? = null): TextFieldData? { + text?.let { + return TextFieldData( + text = text, + size = 14, + font = "NAVI_BODY_DEMI_BOLD", + alignment = "CENTER", + textColor = textColor, + ) + } + return null +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/compose/common/ui/NaviAmcHeader.kt b/android/navi-amc/src/main/java/com/navi/amc/compose/common/ui/NaviAmcHeader.kt new file mode 100644 index 0000000000..e4088534bb --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/compose/common/ui/NaviAmcHeader.kt @@ -0,0 +1,112 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.compose.common.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.IconButton +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.navi.base.model.ActionData +import com.navi.common.R +import com.navi.design.font.FontWeightEnum +import com.navi.design.font.getFontWeight +import com.navi.design.font.naviFontFamily +import com.navi.guarddog.utils.clickableDebounce +import com.navi.naviwidgets.extensions.NaviText + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun NaviAmcHeader( + modifier: Modifier = Modifier, + title: String, + navigationIcon: Int = R.drawable.ic_arrow_left_black_v2, + onNavigationIconClick: () -> Unit, + actionIconId: Int = -1, + actionIconText: String? = null, + showRippleOnActionIcon: Boolean = true, + onActionClick: ((actionData: ActionData?) -> Unit)? = null, + backgroundColor: Color = Color.White, + maxLines: Int = 1, +) { + CenterAlignedTopAppBar( + title = { + Box(Modifier.fillMaxHeight(), contentAlignment = Alignment.Center) { + NaviText( + text = title, + fontSize = 14.sp, + fontFamily = naviFontFamily, + fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), + color = Color.Black, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth().padding(horizontal = 40.dp), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + }, + navigationIcon = { + Box(Modifier.fillMaxHeight(), contentAlignment = Alignment.Center) { + IconButton(onClick = onNavigationIconClick) { + Image(painter = painterResource(navigationIcon), contentDescription = null) + } + } + }, + actions = { + Box(Modifier.fillMaxHeight(), contentAlignment = Alignment.Center) { + if (actionIconId > 0) { + Image( + painter = painterResource(id = actionIconId), + contentDescription = "", + modifier = + Modifier.padding(end = 16.dp).clickableDebounce( + showRipple = showRippleOnActionIcon + ) { + onActionClick?.invoke(ActionData()) + }, + ) + } else if (!actionIconText.isNullOrEmpty()) { + NaviText( + text = actionIconText, + fontSize = 14.sp, + fontFamily = naviFontFamily, + fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD), + color = Color.Black, + textAlign = TextAlign.Center, + modifier = + Modifier.padding(end = 16.dp).clickableDebounce { + onActionClick?.invoke(ActionData()) + }, + ) + } else { + Image( + painter = painterResource(id = navigationIcon), + contentDescription = "", + modifier = Modifier.padding(end = 16.dp).alpha(0f), + ) + } + } + }, + modifier = modifier, + colors = TopAppBarDefaults.mediumTopAppBarColors(containerColor = backgroundColor), + ) +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/compose/common/utils/NaviAmcScreen.kt b/android/navi-amc/src/main/java/com/navi/amc/compose/common/utils/NaviAmcScreen.kt new file mode 100644 index 0000000000..a34db8afa6 --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/compose/common/utils/NaviAmcScreen.kt @@ -0,0 +1,15 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.compose.common.utils + +import com.navi.amc.fundbuy.models.Temperature + +enum class NaviAmcScreen(val screenName: String, val screenTemperature: Temperature) { + AMC_FTUE_EDUCATE_SCREEN("educate_screen", Temperature.ORANGE), + AMC_FTUE_FUND_SELECT_SCREEN("fund_select_screen", Temperature.ORANGE), +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/compose/common/utils/NavigationExt.kt b/android/navi-amc/src/main/java/com/navi/amc/compose/common/utils/NavigationExt.kt new file mode 100644 index 0000000000..80210a3356 --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/compose/common/utils/NavigationExt.kt @@ -0,0 +1,52 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.compose.common.utils + +import com.navi.amc.compose.entry.AmcComposeActivity +import com.navi.amc.compose.entry.NaviAmcRouter +import com.navi.amc.fundbuy.models.Temperature +import com.navi.base.model.ActionData +import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import com.ramcosta.composedestinations.spec.Direction +import com.ramcosta.composedestinations.spec.Route + +fun DestinationsNavigator.clearBackStackUpToAndNavigate( + destination: Direction, + popUpTo: Route, + inclusive: Boolean = true, +) { + navigate(destination) { popUpTo(popUpTo) { this.inclusive = inclusive } } +} + +/** + * Navigates to next screen on the basis of the current screen temperature + * + * GREEN SCREEN -> this denotes a source screen, should be added to stack ORANGE SCREEN -> + * intermediate screen, should be removed from fragment stack RED SCREEN -> destination screen, on + * back press from this screen go back to last green screen find the last green screen and pop till + * then + */ +fun navigateToNextScreen( + actionData: ActionData?, + navigator: DestinationsNavigator, + amcComposeActivity: AmcComposeActivity, + screen: NaviAmcScreen, +) { + val direction = NaviAmcRouter.getDirectionFromCta(actionData, amcComposeActivity) + val screenTemperature = screen.screenTemperature + direction?.let { + when (screenTemperature) { + Temperature.GREEN -> {} + Temperature.ORANGE -> { + navigator.popBackStack() + } + Temperature.RED -> {} + } + navigator.navigate(direction) + } +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/compose/entry/AmcComposeActivity.kt b/android/navi-amc/src/main/java/com/navi/amc/compose/entry/AmcComposeActivity.kt new file mode 100644 index 0000000000..d2a1fc2eb3 --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/compose/entry/AmcComposeActivity.kt @@ -0,0 +1,89 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.compose.entry + +import android.graphics.Color +import android.os.Bundle +import androidx.activity.OnBackPressedCallback +import androidx.activity.compose.setContent +import androidx.activity.viewModels +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.LocalOverscrollConfiguration +import androidx.compose.runtime.CompositionLocalProvider +import androidx.core.view.WindowCompat +import androidx.navigation.NavHostController +import com.navi.amc.common.activity.AmcBaseActivity +import com.navi.base.deeplink.DeepLinkManager +import com.navi.base.deeplink.util.DeeplinkConstants +import com.navi.base.model.CtaData +import com.navi.common.model.HelpBottomSheetData +import com.navi.common.model.ModuleNameV2 +import com.navi.common.utils.screenEnterTransition +import com.navi.common.utils.screenExitTransition +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class AmcComposeActivity : AmcBaseActivity(), BackButtonHandler { + + override val screenName: String + get() = "AmcComposeActivity" + + override val moduleName: ModuleNameV2 + get() = ModuleNameV2.AMC + + lateinit var navController: NavHostController + + override val isNavControllerInitialized: Boolean + get() = ::navController.isInitialized + + private val onBackPressedCallback = + object : OnBackPressedCallback(true) { + // review this + override fun handleOnBackPressed() { + handleBackPress() + } + } + + private val amcComposeViewModel by viewModels() + + @OptIn(ExperimentalFoundationApi::class) + override fun onCreate(savedInstanceState: Bundle?) { + screenEnterTransition() + super.onCreate(savedInstanceState) + WindowCompat.setDecorFitsSystemWindows(window, true) + onBackPressedDispatcher.addCallback(this, onBackPressedCallback) + setContent { + CompositionLocalProvider(LocalOverscrollConfiguration provides null) { + AmcMainScreen(amcComposeActivity = this) + } + } + window.decorView.setBackgroundColor(Color.WHITE) + } + + override fun finish() { + if (isTaskRoot) { + DeepLinkManager.getDeepLinkListener() + ?.navigateTo( + activity = this, + ctaData = CtaData(url = DeeplinkConstants.INVESTMENT), + finish = false, + ) + } + super.finish() + screenExitTransition() + } + + fun onHelpClick(helpBottomSheetData: HelpBottomSheetData?, bundle: Bundle?) { + openHelpInfo(helpBottomSheetData, bundle) + } + + override fun initialiseNavController(navHostController: NavHostController) { + this.navController = navHostController + onNavControllerSet(navHostController) + } +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/compose/entry/AmcComposeViewModel.kt b/android/navi-amc/src/main/java/com/navi/amc/compose/entry/AmcComposeViewModel.kt new file mode 100644 index 0000000000..3272ebfb7a --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/compose/entry/AmcComposeViewModel.kt @@ -0,0 +1,18 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.compose.entry + +import com.navi.amc.common.viewmodel.BaseAmcVM +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class AmcComposeViewModel @Inject constructor() : BaseAmcVM() { + + fun getDefaultScreenName() = "" +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/compose/entry/AmcMainScreen.kt b/android/navi-amc/src/main/java/com/navi/amc/compose/entry/AmcMainScreen.kt new file mode 100644 index 0000000000..b61d165ab0 --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/compose/entry/AmcMainScreen.kt @@ -0,0 +1,95 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.compose.entry + +import androidx.compose.animation.AnimatedContentTransitionScope +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.core.tween +import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.navigation.ModalBottomSheetLayout +import androidx.compose.material.navigation.rememberBottomSheetNavigator +import androidx.compose.material.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.plusAssign +import com.navi.amc.compose.NavGraphs +import com.navi.amc.compose.common.ui.AmcModalBottomSheetLayout +import com.navi.amc.compose.common.utils.NaviAmcScreen +import com.navi.amc.utils.Constant.START_SCREEN_NAME +import com.navi.amc.utils.Constant.TRANSITION_DURATION_IN_MILLIS +import com.ramcosta.composedestinations.DestinationsNavHost +import com.ramcosta.composedestinations.animations.defaults.RootNavGraphDefaultAnimations +import com.ramcosta.composedestinations.animations.rememberAnimatedNavHostEngine +import com.ramcosta.composedestinations.navigation.dependency +import com.ramcosta.composedestinations.spec.NavHostEngine + +@Composable +fun AmcMainScreen(amcComposeActivity: AmcComposeActivity) { + val bottomSheetNavigator = rememberBottomSheetNavigator() + val navController = + rememberNavController().apply { this.navigatorProvider += bottomSheetNavigator } + + ModalBottomSheetLayout( + bottomSheetNavigator = bottomSheetNavigator, + content = { + AmcModalBottomSheetLayout( + sheetState = + rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden), + sheetContent = {}, + ) { + amcComposeActivity.initialiseNavController(navController) + DestinationsNavHost( + startRoute = + NaviAmcRouter.getStartRoute( + amcComposeActivity.intent.getStringExtra(START_SCREEN_NAME) + ?: NaviAmcScreen.AMC_FTUE_EDUCATE_SCREEN.screenName + ), + navGraph = NavGraphs.root, + engine = naviHostEngine(), + navController = amcComposeActivity.navController, + dependenciesContainerBuilder = { dependency(amcComposeActivity) }, + ) + } + }, + ) + // generic error bottomsheet +} + +@OptIn(ExperimentalAnimationApi::class) +@Composable +private fun naviHostEngine(): NavHostEngine { + return rememberAnimatedNavHostEngine( + rootDefaultAnimations = + RootNavGraphDefaultAnimations( + enterTransition = { + slideIntoContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Left, + animationSpec = tween(TRANSITION_DURATION_IN_MILLIS), + ) + }, + exitTransition = { + slideOutOfContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Left, + animationSpec = tween(TRANSITION_DURATION_IN_MILLIS), + ) + }, + popEnterTransition = { + slideIntoContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Right, + animationSpec = tween(TRANSITION_DURATION_IN_MILLIS), + ) + }, + popExitTransition = { + slideOutOfContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Companion.Right, + animationSpec = tween(TRANSITION_DURATION_IN_MILLIS), + ) + }, + ) + ) +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/compose/entry/BackButtonHandler.kt b/android/navi-amc/src/main/java/com/navi/amc/compose/entry/BackButtonHandler.kt new file mode 100644 index 0000000000..c26e5aa0cf --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/compose/entry/BackButtonHandler.kt @@ -0,0 +1,44 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.compose.entry + +import com.navi.base.deeplink.DeepLinkManager +import com.navi.base.deeplink.util.DeeplinkConstants +import com.navi.base.model.CtaData + +internal interface BackButtonHandler { + val isNavControllerInitialized: Boolean + + fun AmcComposeActivity.handleBackPress() { + when { + isBackPressedOnAmcRootScreen() -> { + if (isTaskRoot) { + goToInvestmentTab() + } else { + finish() + } + } + else -> { + finish() + } + } + } + + private fun AmcComposeActivity.isBackPressedOnAmcRootScreen(): Boolean { + return isNavControllerInitialized + } + + private fun AmcComposeActivity.goToInvestmentTab() { + DeepLinkManager.getDeepLinkListener() + ?.navigateTo( + activity = this, + ctaData = CtaData(url = DeeplinkConstants.INVESTMENT), + finish = true, + ) + } +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/compose/entry/NaviAmcRouter.kt b/android/navi-amc/src/main/java/com/navi/amc/compose/entry/NaviAmcRouter.kt new file mode 100644 index 0000000000..022f642013 --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/compose/entry/NaviAmcRouter.kt @@ -0,0 +1,59 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.compose.entry + +import com.navi.amc.compose.common.utils.NaviAmcScreen +import com.navi.amc.compose.destinations.AmcRouterScreenDestination +import com.navi.amc.compose.destinations.DirectionDestination +import com.navi.amc.compose.destinations.FtueEducateScreenDestination +import com.navi.amc.compose.destinations.FtueFundSelectScreenDestination +import com.navi.amc.navigator.NaviAmcDeeplinkNavigator.FTUE +import com.navi.amc.utils.toNavigateAmcModule +import com.navi.base.model.ActionData +import com.ramcosta.composedestinations.spec.Route + +object NaviAmcRouter { + + fun getStartRoute(startScreenName: String): Route { + return when (startScreenName) { + NaviAmcScreen.AMC_FTUE_EDUCATE_SCREEN.screenName -> FtueEducateScreenDestination + NaviAmcScreen.AMC_FTUE_FUND_SELECT_SCREEN.screenName -> FtueFundSelectScreenDestination + else -> AmcRouterScreenDestination + } + } + + fun onCtaClick(amcComposeActivity: AmcComposeActivity, actionData: ActionData?) { + actionData?.toNavigateAmcModule(amcComposeActivity) + } + + fun getDirectionFromCta( + actionData: ActionData?, + amcComposeActivity: AmcComposeActivity, + ): DirectionDestination? { + val deepLink = actionData?.url + val splitDeepLink = deepLink?.split("/") + val firstIdentifier = splitDeepLink?.getOrNull(1) + val secondIdentifier = splitDeepLink?.getOrNull(2) + return when (firstIdentifier) { + FTUE -> { + when (secondIdentifier) { + NaviAmcScreen.AMC_FTUE_EDUCATE_SCREEN.screenName -> FtueEducateScreenDestination + NaviAmcScreen.AMC_FTUE_FUND_SELECT_SCREEN.screenName -> + FtueFundSelectScreenDestination + else -> { + null + } + } + } + else -> { + actionData?.toNavigateAmcModule(activity = amcComposeActivity, finish = true) + return null + } + } + } +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/compose/feature/ftue/FtueRepository.kt b/android/navi-amc/src/main/java/com/navi/amc/compose/feature/ftue/FtueRepository.kt new file mode 100644 index 0000000000..1e2bfdd113 --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/compose/feature/ftue/FtueRepository.kt @@ -0,0 +1,40 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.compose.feature.ftue + +import com.navi.amc.compose.common.model.AmcGenericScreenResponse +import com.navi.amc.compose.feature.ftue.model.FundSelectionData +import com.navi.amc.network.retrofit.RetrofitService +import com.navi.amc.utils.getAmcMetricInfo +import com.navi.common.network.models.RepoResult +import com.navi.common.network.models.SuccessResponse +import com.navi.common.network.retrofit.ResponseCallback +import javax.inject.Inject + +class FtueRepository @Inject constructor(private val retrofitService: RetrofitService) : + ResponseCallback() { + suspend fun fetchFtueEducateScreenData(): RepoResult = + apiResponseCallback( + response = retrofitService.fetchFtueEducateScreenData(), + metricInfo = getAmcMetricInfo(), + ) + + suspend fun fetchFtueFundSelectScreenData(): RepoResult = + apiResponseCallback( + response = retrofitService.fetchFtueFundSelectScreenData(), + metricInfo = getAmcMetricInfo(), + ) + + suspend fun postSelectedFund( + fundSelectionData: FundSelectionData + ): RepoResult = + apiResponseCallback( + response = retrofitService.postSelectedFund(fundSelectionData), + metricInfo = getAmcMetricInfo(), + ) +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/compose/feature/ftue/model/FundSelectionData.kt b/android/navi-amc/src/main/java/com/navi/amc/compose/feature/ftue/model/FundSelectionData.kt new file mode 100644 index 0000000000..696f3845a0 --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/compose/feature/ftue/model/FundSelectionData.kt @@ -0,0 +1,12 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.compose.feature.ftue.model + +import com.google.gson.annotations.SerializedName + +data class FundSelectionData(@SerializedName("isin") val isin: String? = null) diff --git a/android/navi-amc/src/main/java/com/navi/amc/compose/feature/ftue/ui/FtueEducateScreen.kt b/android/navi-amc/src/main/java/com/navi/amc/compose/feature/ftue/ui/FtueEducateScreen.kt new file mode 100644 index 0000000000..acc65c987d --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/compose/feature/ftue/ui/FtueEducateScreen.kt @@ -0,0 +1,159 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.compose.feature.ftue.ui + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.imePadding +import androidx.compose.material.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.navi.amc.common.composables.AmcCommonWidgetListRenderer +import com.navi.amc.compose.common.model.AmcGenericScreenResponse +import com.navi.amc.compose.common.model.ScreenState +import com.navi.amc.compose.common.ui.AmcShimmer +import com.navi.amc.compose.common.ui.NaviAmcFooter +import com.navi.amc.compose.common.ui.NaviAmcHeader +import com.navi.amc.compose.common.ui.SetStatusBarColor +import com.navi.amc.compose.common.utils.NaviAmcScreen +import com.navi.amc.compose.common.utils.navigateToNextScreen +import com.navi.amc.compose.entry.AmcComposeActivity +import com.navi.amc.compose.entry.NaviAmcRouter +import com.navi.amc.compose.feature.ftue.viewmodel.FtueViewModel +import com.navi.amc.fundbuy.models.AmcHeaderExtraData +import com.navi.amc.utils.AmcAnalytics.sendEvent +import com.navi.base.model.ActionData +import com.navi.base.utils.EMPTY +import com.navi.common.ui.errorview.FullScreenErrorComposeView +import com.navi.naviwidgets.extensions.hexToColor +import com.navi.naviwidgets.extensions.hexToInt +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.annotation.RootNavGraph +import com.ramcosta.composedestinations.navigation.DestinationsNavigator + +@RootNavGraph(start = true) +@Destination +@Composable +fun FtueEducateScreen( + amcComposeActivity: AmcComposeActivity, + navigator: DestinationsNavigator, + ftueViewModel: FtueViewModel = hiltViewModel(), +) { + val ftueEducateScreenState by ftueViewModel.ftueEducateScreenState.collectAsStateWithLifecycle() + + LaunchedEffect(key1 = ftueEducateScreenState) { + if (ftueEducateScreenState is ScreenState.Empty) { + ftueViewModel.fetchFtueEducateScreenData() + } + } + + val onFooterNextCtaClicked = { actionData: ActionData? -> + sendEvent( + actionData?.metaData?.clickedData, + screenName = NaviAmcScreen.AMC_FTUE_EDUCATE_SCREEN.screenName, + ) + navigateToNextScreen( + actionData = actionData, + navigator = navigator, + amcComposeActivity = amcComposeActivity, + screen = NaviAmcScreen.AMC_FTUE_EDUCATE_SCREEN, + ) + } + + val onBackPressed = { + // analytics + amcComposeActivity.finish() + } + + BackHandler { onBackPressed() } + + val onHelpClicked = { actionData: ActionData? -> + amcComposeActivity.onHelpClick(helpBottomSheetData = null, bundle = null) + } + + Column(modifier = Modifier.fillMaxSize()) { + when (ftueEducateScreenState) { + is ScreenState.Loading -> { + AmcShimmer() + } + is ScreenState.Success -> { + val data = ftueEducateScreenState as ScreenState.Success + FtueEducateScreenRenderer( + amcComposeActivity = amcComposeActivity, + amcGenericScreenResponse = data.amcGenericScreenResponse, + onBackPressed = onBackPressed, + onHelpClicked = { onHelpClicked(it) }, + onFooterNextCtaClicked = onFooterNextCtaClicked, + ) + } + is ScreenState.Error -> { + val data = ftueEducateScreenState as ScreenState.Error + FullScreenErrorComposeView( + error = data.error, + onRetryClick = { ftueViewModel.fetchFtueEducateScreenData() }, + ) + } + else -> {} + } + } +} + +@Composable +fun FtueEducateScreenRenderer( + amcComposeActivity: AmcComposeActivity, + amcGenericScreenResponse: AmcGenericScreenResponse, + onBackPressed: () -> Unit, + onHelpClicked: ((actionData: ActionData?) -> Unit)? = null, + onFooterNextCtaClicked: ((actionData: ActionData?) -> Unit)? = null, + onFooterBackCtaClicked: ((actionData: ActionData?) -> Unit)? = null, +) { + SetStatusBarColor(amcComposeActivity, hexToInt(amcGenericScreenResponse.header?.bgColor)) + Scaffold( + modifier = Modifier.fillMaxSize().imePadding(), + topBar = { + NaviAmcHeader( + title = EMPTY, + onNavigationIconClick = onBackPressed, + actionIconText = "HELP", + onActionClick = onHelpClicked, + backgroundColor = hexToColor(amcGenericScreenResponse.header?.bgColor), + ) + }, + content = { innerPadding -> + AmcCommonWidgetListRenderer( + widgetList = amcGenericScreenResponse.content, + actionListener = { actionData -> + NaviAmcRouter.onCtaClick( + amcComposeActivity = amcComposeActivity, + actionData = actionData, + ) + }, + extraData = + AmcHeaderExtraData( + header = amcGenericScreenResponse.header, + toggleStatusBarAndHeaderData = { color, showTitle -> }, + showHeaderTitle = false, + toggleShadow = { shadowState -> }, + ), + eventHandler = { actionData -> }, + ) + }, + bottomBar = { + NaviAmcFooter( + footer = amcGenericScreenResponse.footer, + onNextCtaClicked = onFooterNextCtaClicked, + onBackCtaClicked = onFooterBackCtaClicked, + ) + }, + ) +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/compose/feature/ftue/ui/FtueFundSelectScreen.kt b/android/navi-amc/src/main/java/com/navi/amc/compose/feature/ftue/ui/FtueFundSelectScreen.kt new file mode 100644 index 0000000000..15a4273bbf --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/compose/feature/ftue/ui/FtueFundSelectScreen.kt @@ -0,0 +1,190 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.compose.feature.ftue.ui + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.imePadding +import androidx.compose.material.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.navi.amc.common.activity.CheckerActivity +import com.navi.amc.common.composables.AmcCommonWidgetListRenderer +import com.navi.amc.compose.common.model.AmcGenericScreenResponse +import com.navi.amc.compose.common.model.ScreenState +import com.navi.amc.compose.common.ui.AmcShimmer +import com.navi.amc.compose.common.ui.NaviAmcFooter +import com.navi.amc.compose.common.ui.NaviAmcHeader +import com.navi.amc.compose.common.ui.SetStatusBarColor +import com.navi.amc.compose.common.utils.NaviAmcScreen +import com.navi.amc.compose.common.utils.navigateToNextScreen +import com.navi.amc.compose.entry.AmcComposeActivity +import com.navi.amc.compose.feature.ftue.viewmodel.FtueViewModel +import com.navi.amc.fundbuy.models.AmcHeaderExtraData +import com.navi.amc.fundbuy.models.SelectionData +import com.navi.amc.navigator.NaviAmcDeeplinkNavigator +import com.navi.amc.navigator.NaviAmcDeeplinkNavigator.FTUE +import com.navi.amc.utils.AmcAnalytics.ISIN +import com.navi.amc.utils.AmcAnalytics.sendEvent +import com.navi.amc.utils.Constant +import com.navi.amc.utils.TempStorageHelper +import com.navi.base.model.ActionData +import com.navi.base.utils.EMPTY +import com.navi.base.utils.orFalse +import com.navi.common.ui.errorview.FullScreenErrorComposeView +import com.navi.naviwidgets.extensions.hexToInt +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.navigation.DestinationsNavigator + +@Destination +@Composable +fun FtueFundSelectScreen( + amcComposeActivity: AmcComposeActivity, + navigator: DestinationsNavigator, + ftueViewModel: FtueViewModel = hiltViewModel(), +) { + val ftueFundSelectScreenState by + ftueViewModel.ftueFundSelectScreenState.collectAsStateWithLifecycle() + + LaunchedEffect(key1 = ftueFundSelectScreenState) { + if (ftueFundSelectScreenState is ScreenState.Empty) { + ftueViewModel.fetchFtueFundSelectScreenState() + } + } + + val onFooterNextCtaClicked = { actionData: ActionData? -> + ftueViewModel.postSelectedFund() + sendEvent( + actionData?.metaData?.clickedData, + screenName = NaviAmcScreen.AMC_FTUE_FUND_SELECT_SCREEN.screenName, + ) + if ( + actionData + ?.url + ?.contains( + NaviAmcDeeplinkNavigator.AMC.plus("/").plus(NaviAmcDeeplinkNavigator.KYC), + true, + ) + .orFalse() || + actionData?.url?.contains(CheckerActivity.HPC_PAN_REDIRECTION_PAGE).orFalse() || + actionData?.url?.contains(CheckerActivity.HPC_NAME_REDIRECTION_PAGE).orFalse() + ) { + TempStorageHelper.kycSourceInfo = + mapOf( + Constant.KYC_SOURCE_SCREEN to FTUE, + ISIN to ftueViewModel.selectedIsin.orEmpty(), + ) + } + navigateToNextScreen( + actionData = actionData, + navigator = navigator, + amcComposeActivity = amcComposeActivity, + screen = NaviAmcScreen.AMC_FTUE_FUND_SELECT_SCREEN, + ) + } + + val onHelpClicked = { actionData: ActionData? -> + amcComposeActivity.onHelpClick(helpBottomSheetData = null, bundle = null) + } + + val onBackPressed = { + // analytics + amcComposeActivity.finish() + Unit + } + + BackHandler { onBackPressed() } + + val onFundSelected = { actionData: ActionData? -> + val isin = actionData?.parameters?.find { it.key == ISIN }?.value + ftueViewModel.updateSelectedIsin(isin) + } + + val selectedIsin = ftueViewModel.selectedIsin + + Column(modifier = Modifier.fillMaxSize()) { + when (ftueFundSelectScreenState) { + is ScreenState.Loading -> { + AmcShimmer() + } + is ScreenState.Success -> { + val data = ftueFundSelectScreenState as ScreenState.Success + FtueFundSelectScreenRenderer( + amcComposeActivity = amcComposeActivity, + amcGenericScreenResponse = data.amcGenericScreenResponse, + onFundSelected = onFundSelected, + onBackPressed = onBackPressed, + onHelpClicked = onHelpClicked, + onFooterNextCtaClicked = onFooterNextCtaClicked, + selectedIsin = selectedIsin, + ) + } + is ScreenState.Error -> { + val data = ftueFundSelectScreenState as ScreenState.Error + FullScreenErrorComposeView( + error = data.error, + onRetryClick = { ftueViewModel.fetchFtueFundSelectScreenState() }, + ) + } + else -> {} + } + } +} + +@Composable +fun FtueFundSelectScreenRenderer( + amcComposeActivity: AmcComposeActivity, + amcGenericScreenResponse: AmcGenericScreenResponse, + onFundSelected: ((actionData: ActionData?) -> Unit)? = null, + onBackPressed: () -> Unit, + onHelpClicked: ((actionData: ActionData?) -> Unit)? = null, + onFooterNextCtaClicked: ((actionData: ActionData?) -> Unit)? = null, + onFooterBackCtaClicked: ((actionData: ActionData?) -> Unit)? = null, + selectedIsin: String? = null, +) { + SetStatusBarColor(amcComposeActivity, hexToInt(amcGenericScreenResponse.header?.bgColor)) + Scaffold( + modifier = Modifier.fillMaxSize().imePadding(), + topBar = { + // generalise this function to receive header dto + NaviAmcHeader( + title = EMPTY, + onNavigationIconClick = onBackPressed, + actionIconText = amcGenericScreenResponse.header?.help?.title?.text ?: EMPTY, + onActionClick = onHelpClicked, + ) + }, + content = { innerPadding -> + AmcCommonWidgetListRenderer( + widgetList = amcGenericScreenResponse.content, + actionListener = { actionData -> onFundSelected?.invoke(actionData) }, + extraData = + AmcHeaderExtraData( + header = amcGenericScreenResponse.header, + toggleStatusBarAndHeaderData = { color, showTitle -> }, + showHeaderTitle = false, + toggleShadow = { shadowState -> }, + ), + eventHandler = { actionData -> }, + selectionExtraData = SelectionData(selectedId = selectedIsin), + ) + }, + bottomBar = { + NaviAmcFooter( + footer = amcGenericScreenResponse.footer, + onNextCtaClicked = onFooterNextCtaClicked, + onBackCtaClicked = onFooterBackCtaClicked, + ) + }, + ) +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/compose/feature/ftue/viewmodel/FtueViewModel.kt b/android/navi-amc/src/main/java/com/navi/amc/compose/feature/ftue/viewmodel/FtueViewModel.kt new file mode 100644 index 0000000000..5a61186728 --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/compose/feature/ftue/viewmodel/FtueViewModel.kt @@ -0,0 +1,105 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.compose.feature.ftue.viewmodel + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.viewModelScope +import com.navi.amc.common.viewmodel.BaseAmcVM +import com.navi.amc.compose.common.model.ScreenState +import com.navi.amc.compose.feature.ftue.FtueRepository +import com.navi.amc.compose.feature.ftue.model.FundSelectionData +import com.navi.common.network.models.isSuccessWithData +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +@HiltViewModel +class FtueViewModel @Inject constructor(private val ftueRepository: FtueRepository) : BaseAmcVM() { + + // implement caching + + private val _ftueEducateScreenState = MutableStateFlow(ScreenState.Empty) + val ftueEducateScreenState = _ftueEducateScreenState.asStateFlow() + + private val _ftueFundSelectScreenState = MutableStateFlow(ScreenState.Empty) + val ftueFundSelectScreenState = _ftueFundSelectScreenState.asStateFlow() + + var selectedIsin by mutableStateOf(null) + private set + + private fun updateFtueEducateScreenState(state: ScreenState) { + _ftueEducateScreenState.value = state + } + + private fun updateFtueFundSelectScreenState(state: ScreenState) { + _ftueFundSelectScreenState.value = state + } + + fun updateSelectedIsin(isin: String?) { + isin?.let { selectedIsin = isin } + } + + fun postSelectedFund() { + val isin = selectedIsin + isin?.let { + viewModelScope.safeLaunch { + if (isin.isNotEmpty()) { + val fundSelectionData = FundSelectionData(isin = isin) + ftueRepository.postSelectedFund(fundSelectionData) + } + } + } + } + + fun fetchFtueEducateScreenData() { + viewModelScope.safeLaunch { + updateFtueEducateScreenState(ScreenState.Loading) + + val ftueEducateResponse = ftueRepository.fetchFtueEducateScreenData() + + if (ftueEducateResponse.isSuccessWithData()) { + updateFtueEducateScreenState( + ScreenState.Success( + amcGenericScreenResponse = ftueEducateResponse.data ?: return@safeLaunch + ) + ) + } else { + updateFtueEducateScreenState( + ScreenState.Error( + error = ftueEducateResponse.errors?.firstOrNull() ?: return@safeLaunch + ) + ) + } + } + } + + fun fetchFtueFundSelectScreenState() { + viewModelScope.safeLaunch { + updateFtueFundSelectScreenState(ScreenState.Loading) + + val ftueFundSelectResponse = ftueRepository.fetchFtueFundSelectScreenData() + + if (ftueFundSelectResponse.isSuccessWithData()) { + updateFtueFundSelectScreenState( + ScreenState.Success( + amcGenericScreenResponse = ftueFundSelectResponse.data ?: return@safeLaunch + ) + ) + } else { + updateFtueFundSelectScreenState( + ScreenState.Error( + error = ftueFundSelectResponse.errors?.firstOrNull() ?: return@safeLaunch + ) + ) + } + } + } +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/cards/PaymentCardComposable.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/cards/PaymentCardComposable.kt new file mode 100644 index 0000000000..3d385d1500 --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/cards/PaymentCardComposable.kt @@ -0,0 +1,233 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.fundbuy.composables.cards + +/* + * + * * Copyright © 2024-2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +import PaymentCard +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material.Card +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.DefaultShadowColor +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.navi.amc.utils.Constant.UPCOMING_SIP_PAYMENT_CARD +import com.navi.base.model.ActionData +import com.navi.base.utils.isNotNull +import com.navi.base.utils.orFalse +import com.navi.base.utils.orTrue +import com.navi.design.utils.clickableWithNoGesture +import com.navi.naviwidgets.extensions.NaviImage +import com.navi.naviwidgets.extensions.NaviTextWidgetized +import com.navi.uitron.utils.ShapeUtil +import com.navi.uitron.utils.getBorderStrokeBrushData +import com.navi.uitron.utils.hexToComposeColor +import com.navi.uitron.utils.setBackground +import com.navi.uitron.utils.setHorizontalArrangement +import com.navi.uitron.utils.setPadding + +@Composable +fun PaymentCardComposable( + paymentCard: PaymentCard, + cardWidth: Dp = LocalConfiguration.current.screenWidthDp.dp, + onClick: (actionData: ActionData?) -> Unit, + cardType: String? = UPCOMING_SIP_PAYMENT_CARD, +) { + + if (paymentCard.properties?.cardProperty?.visible.orTrue().not()) { + return + } + + Card( + shape = ShapeUtil.run { getShape(shape = paymentCard.properties?.cardProperty?.shape) }, + elevation = 0.dp, + backgroundColor = + paymentCard.properties?.cardProperty?.backgroundColor?.hexToComposeColor ?: Color.White, + border = + BorderStroke( + width = + ((paymentCard.properties?.cardProperty?.borderStrokeData?.width) ?: 0.0f).dp, + brush = + getBorderStrokeBrushData(paymentCard.properties?.cardProperty?.borderStrokeData), + ), + modifier = + Modifier.width(cardWidth) + .shadow( + elevation = (paymentCard.properties?.cardProperty?.elevation ?: 0).dp, + ambientColor = + paymentCard.properties?.cardProperty?.ambientColor?.hexToComposeColor + ?: DefaultShadowColor, + spotColor = + paymentCard.properties?.cardProperty?.spotColor?.hexToComposeColor + ?: DefaultShadowColor, + shape = ShapeUtil.getShape(shape = paymentCard.properties?.cardProperty?.shape), + ) + .clickableWithNoGesture(onClick = { onClick(paymentCard.actionData) }), + ) { + Column( + modifier = + Modifier.padding( + top = (paymentCard.properties?.cardProperty?.padding?.top ?: 0).dp, + bottom = (paymentCard.properties?.cardProperty?.padding?.bottom ?: 0).dp, + start = (paymentCard.properties?.cardProperty?.padding?.start ?: 0).dp, + end = (paymentCard.properties?.cardProperty?.padding?.end ?: 0).dp, + ) + ) { + if ( + paymentCard.paymentCardHeader.isNotNull() && + paymentCard.properties?.paymentCardHeaderProperty?.visible.orTrue() + ) { + PaymentCardHeader(paymentCard = paymentCard) + } + + PaymentDetailsComposable(paymentCard = paymentCard, cardType = cardType) + } + } +} + +@Composable +fun PaymentDetailsComposable(paymentCard: PaymentCard, cardType: String?) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically, + ) { + if (paymentCard.properties?.investmentsIconProperty?.visible.orFalse()) { + paymentCard.investmentsIcon?.let { + NaviImage( + imageFieldData = it, + modifier = + Modifier.width((it.iconWidth ?: 12).dp).height((it.iconHeight ?: 12).dp), + ) + + Spacer( + modifier = + Modifier.wrapContentHeight() + .width( + (paymentCard.properties?.investmentsIconProperty?.margin?.end ?: 8) + .dp + ) + ) + } + } + Column( + modifier = Modifier.weight(paymentCard.properties?.cardProperty?.cardWeight ?: 1.0f) + ) { + if (cardType == "NORMAL_CARD") { + NaviTextWidgetized(textFieldData = paymentCard.fundName) + } else if (cardType == UPCOMING_SIP_PAYMENT_CARD) { + Row(verticalAlignment = Alignment.CenterVertically) { + NaviTextWidgetized(textFieldData = paymentCard.paymentAmount) + NaviTextWidgetized(textFieldData = paymentCard.fundName) + } + NaviTextWidgetized( + textFieldData = paymentCard.paymentCardSubtitle, + modifier = + Modifier.height((paymentCard.paymentCardSubtitle?.lineSpacing ?: 16).dp), + ) + } else { + NaviTextWidgetized( + textFieldData = paymentCard.fundName, + modifier = Modifier.height((paymentCard.fundName?.lineSpacing ?: 16).dp), + ) + + paymentCard.paymentAmount?.let { + NaviTextWidgetized( + textFieldData = paymentCard.paymentAmount, + modifier = + Modifier.height((paymentCard.paymentAmount?.lineSpacing ?: 16).dp), + ) + } + } + } + + Spacer( + modifier = + Modifier.weight((1 - (paymentCard.properties?.cardProperty?.cardWeight ?: 1.0f))) + ) + + NaviTextWidgetized( + textFieldData = paymentCard.buttonText, + modifier = + Modifier.setBackground( + paymentCard.properties?.buttonProperty?.backgroundColor, + paymentCard.properties?.buttonProperty?.shape, + paymentCard.properties?.buttonProperty?.backGroundBrushData, + ) + .padding( + top = (paymentCard.properties?.buttonProperty?.padding?.top ?: 0).dp, + bottom = (paymentCard.properties?.buttonProperty?.padding?.bottom ?: 0).dp, + start = (paymentCard.properties?.buttonProperty?.padding?.start ?: 0).dp, + end = (paymentCard.properties?.buttonProperty?.padding?.end ?: 0).dp, + ), + ) + } +} + +@Composable +fun PaymentCardHeader(paymentCard: PaymentCard) { + Row( + modifier = + Modifier.setPadding(paymentCard.properties?.paymentCardHeaderProperty?.padding) + .fillMaxWidth(), + horizontalArrangement = + Arrangement.setHorizontalArrangement( + arrangementData = paymentCard.properties?.paymentCardHeaderProperty?.arrangementData + ), + ) { + paymentCard.paymentCardHeader?.paymentType?.let { + NaviTextWidgetized( + modifier = + Modifier.setBackground( + paymentCard.properties?.paymentTypeProperty?.backgroundColor, + paymentCard.properties?.paymentTypeProperty?.shape, + paymentCard.properties?.paymentTypeProperty?.backGroundBrushData, + ) + .padding( + start = + (paymentCard.properties?.paymentTypeProperty?.padding?.start ?: 0) + .dp, + end = + (paymentCard.properties?.paymentTypeProperty?.padding?.end ?: 0).dp, + top = + (paymentCard.properties?.paymentTypeProperty?.padding?.top ?: 0).dp, + bottom = + (paymentCard.properties?.paymentTypeProperty?.padding?.bottom ?: 0) + .dp, + ), + textFieldData = it, + ) + } + + paymentCard.paymentCardHeader?.paymentDate?.let { + NaviTextWidgetized( + textFieldData = it, + modifier = Modifier.setPadding(paymentCard.properties?.paymentDateProperty?.padding), + ) + } + } +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/BannerWidgetComposable.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/BannerWidgetComposable.kt similarity index 73% rename from android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/BannerWidgetComposable.kt rename to android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/BannerWidgetComposable.kt index 3a3de80241..4fb97cdd72 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/BannerWidgetComposable.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/BannerWidgetComposable.kt @@ -5,12 +5,11 @@ * */ -package com.navi.amc.fundbuy.composables +package com.navi.amc.fundbuy.composables.widgets import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -42,10 +41,19 @@ fun BannerWidgetComposable(widget: TitleSubtitleImageWidget?) { NaviImage( imageFieldData = data.image, modifier = - Modifier.setPadding( - data.properties?.titleProperty?.padding - ?: ComposePadding(top = 16, bottom = 16) - ), + Modifier.then( + if (data.properties?.imageProperty?.cardWeight != null) { + Modifier.fillMaxWidth( + data.properties.imageProperty.cardWeight ?: 1f + ) + } else { + Modifier + } + ) + .setPadding( + data.properties?.titleProperty?.padding + ?: ComposePadding(top = 16, bottom = 16) + ), ) NaviTextWidgetized( textFieldData = data.subTitle, diff --git a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/BannerWithLottieWidgetComposable.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/BannerWithLottieWidgetComposable.kt new file mode 100644 index 0000000000..9be169eefe --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/BannerWithLottieWidgetComposable.kt @@ -0,0 +1,61 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.fundbuy.composables.widgets + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.navi.amc.fundbuy.models.widgets.TitleSubtitleLottieWidget +import com.navi.naviwidgets.extensions.NaviLottie +import com.navi.naviwidgets.extensions.NaviTextWidgetized +import com.navi.naviwidgets.extensions.hexToColor +import com.navi.uitron.model.ui.ComposePadding +import com.navi.uitron.utils.ShapeUtil +import com.navi.uitron.utils.setPadding + +@Composable +fun BannerWithLottieWidgetComposable(widget: TitleSubtitleLottieWidget?) { + widget?.widgetData?.let { data -> + Column( + modifier = + Modifier.fillMaxWidth() + .background( + color = hexToColor(data.properties?.cardProperty?.backgroundColor), + shape = ShapeUtil.getShape(data.properties?.cardProperty?.shape), + ) + .setPadding( + data.properties?.cardProperty?.padding + ?: ComposePadding(start = 16, end = 16, top = 12) + ), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + NaviTextWidgetized(textFieldData = data.title) + data.lottie?.let { + NaviLottie( + lottie = it, + modifier = + Modifier.fillMaxWidth() + .setPadding( + data.properties?.lottieProperty?.padding + ?: ComposePadding(top = 16, bottom = 16) + ), + ) + } + NaviTextWidgetized( + textFieldData = data.subTitle, + modifier = + Modifier.setPadding( + data.properties?.subTitleProperty?.padding ?: ComposePadding(bottom = 24) + ), + ) + } + } +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/FooterWidgetComposable.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/FooterWidgetComposable.kt similarity index 97% rename from android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/FooterWidgetComposable.kt rename to android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/FooterWidgetComposable.kt index 49bc164652..e3fc25e429 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/FooterWidgetComposable.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/FooterWidgetComposable.kt @@ -5,7 +5,7 @@ * */ -package com.navi.amc.fundbuy.composables +package com.navi.amc.fundbuy.composables.widgets import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth diff --git a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/HelpInfoWidgetComposable.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/HelpInfoWidgetComposable.kt similarity index 98% rename from android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/HelpInfoWidgetComposable.kt rename to android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/HelpInfoWidgetComposable.kt index 574a40f45b..96f7c27970 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/HelpInfoWidgetComposable.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/HelpInfoWidgetComposable.kt @@ -5,7 +5,7 @@ * */ -package com.navi.amc.fundbuy.composables +package com.navi.amc.fundbuy.composables.widgets import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement diff --git a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/OnTimeAssuranceWidgetComposable.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/OnTimeAssuranceWidgetComposable.kt similarity index 98% rename from android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/OnTimeAssuranceWidgetComposable.kt rename to android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/OnTimeAssuranceWidgetComposable.kt index 97262689f5..857bf99abb 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/OnTimeAssuranceWidgetComposable.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/OnTimeAssuranceWidgetComposable.kt @@ -5,7 +5,7 @@ * */ -package com.navi.amc.fundbuy.composables +package com.navi.amc.fundbuy.composables.widgets import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Canvas @@ -15,7 +15,6 @@ 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.material.Card import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier diff --git a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/RepeatOrderWidgetComposable.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/RepeatOrderWidgetComposable.kt new file mode 100644 index 0000000000..f8a49b7ad9 --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/RepeatOrderWidgetComposable.kt @@ -0,0 +1,101 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.fundbuy.composables.widgets + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp +import com.navi.amc.common.composables.AmcCardListComposable +import com.navi.amc.fundbuy.models.widgets.RepeatOrderWidget +import com.navi.amc.utils.Constant.DEFAULT_CARD_WIDTH_FACTOR +import com.navi.amc.utils.Constant.FREE_SCROLL +import com.navi.amc.utils.Constant.REPEAT_ORDER +import com.navi.base.model.ActionData +import com.navi.base.model.GenericAnalytics +import com.navi.naviwidgets.extensions.NaviTextWidgetized +import com.navi.naviwidgets.extensions.hexToColor +import com.navi.uitron.utils.setPadding + +@Composable +fun RepeatOrderWidgetComposable( + widget: RepeatOrderWidget, + onClick: (actionData: ActionData?) -> Unit, + onVisible: ((genericAnalytics: GenericAnalytics?) -> Unit)? = null, +) { + widget.widgetData?.let { + Column(modifier = Modifier.setPadding(it.extraData?.property?.padding)) { + val listState = rememberLazyListState() + val pagerState = + rememberPagerState(pageCount = { it.content?.repeatOrderCardList?.size ?: 0 }) + NaviTextWidgetized( + textFieldData = it.header?.title, + modifier = Modifier.setPadding(it.header?.property?.padding), + ) + + AmcCardListComposable( + cardType = REPEAT_ORDER, + cardWidthFactor = + (it.extraData?.property?.cardWidthFactor ?: DEFAULT_CARD_WIDTH_FACTOR), + cardList = it.content?.repeatOrderCardList, + onClick = onClick, + scrollType = it.extraData?.scrollType ?: FREE_SCROLL, + listState = listState, + pagerState = pagerState, + ) + + it.content?.repeatOrderCardList?.size?.let { listSize -> + it.content.carouselData?.let { carouselData -> + Row( + horizontalArrangement = Arrangement.Center, + modifier = + Modifier.setPadding(carouselData.carouselProperty?.padding) + .fillMaxWidth(), + ) { + repeat(listSize) { index -> + Box( + modifier = + Modifier.size( + width = + if (index == pagerState.currentPage) { + (carouselData.activeCarouselWidth ?: 24).dp + } else { + (carouselData.inactiveCarouselWidth ?: 16).dp + }, + height = (carouselData.carouselHeight ?: 3).dp, + ) + .clip(CircleShape) + .background( + if (index == pagerState.currentPage) { + hexToColor(carouselData.activeCarouselColor) + } else hexToColor(carouselData.inactiveCarouselColor) + ) + .padding(horizontal = 4.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + } + } + } + } + } + } +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/TitleContentRadioWidgetComposable.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/TitleContentRadioWidgetComposable.kt new file mode 100644 index 0000000000..f09c7dd1f4 --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/composables/widgets/TitleContentRadioWidgetComposable.kt @@ -0,0 +1,183 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.fundbuy.composables.widgets + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +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.width +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.navi.amc.fundbuy.models.SelectionData +import com.navi.amc.fundbuy.models.widgets.TitleContentRadioWidget +import com.navi.base.model.ActionData +import com.navi.base.model.LineItem +import com.navi.base.utils.orZero +import com.navi.design.utils.clickableWithNoGesture +import com.navi.naviwidgets.extensions.NaviImage +import com.navi.naviwidgets.extensions.NaviTextWidgetized +import com.navi.naviwidgets.extensions.hexToColor +import com.navi.uitron.model.ui.ComposePadding +import com.navi.uitron.utils.ShapeUtil +import com.navi.uitron.utils.setPadding + +@Composable +fun TitleContentRadioWidgetComposable( + widget: TitleContentRadioWidget?, + onClick: (actionData: ActionData?) -> Unit, + selectionData: SelectionData? = null, +) { + LaunchedEffect(Unit) { + if (widget?.widgetData?.selectionProperties?.isSelected == true) { + onClick( + ActionData( + parameters = + listOf( + LineItem( + key = "isin", + value = widget.widgetData.selectionProperties.isin, + ) + ) + ) + ) + } + } + widget?.widgetData?.let { data -> + Column( + modifier = + Modifier.fillMaxWidth() + .clickableWithNoGesture { + onClick( + ActionData( + parameters = + listOf( + LineItem( + key = "isin", + value = data.selectionProperties?.isin, + ) + ) + ) + ) + } + .background( + color = hexToColor(data.properties?.cardProperty?.backgroundColor), + shape = ShapeUtil.getShape(data.properties?.cardProperty?.shape), + ) + .setPadding( + data.properties?.cardProperty?.padding + ?: ComposePadding(start = 16, end = 16, top = 16, bottom = 16) + ) + .border( + width = data.properties?.cardProperty?.borderStrokeData?.width?.dp ?: 0.dp, + color = hexToColor(data.properties?.cardProperty?.borderStrokeData?.color), + shape = + ShapeUtil.getShape( + data.properties?.cardProperty?.borderStrokeData?.shape + ), + ), + horizontalAlignment = Alignment.Start, + ) { + Row( + modifier = + Modifier.fillMaxWidth() + .setPadding( + data.properties?.titleProperty?.padding + ?: ComposePadding(start = 16, end = 16, top = 16, bottom = 4) + ), + verticalAlignment = Alignment.CenterVertically, + ) { + data.title?.let { NaviTextWidgetized(textFieldData = it) } + Spacer(modifier = Modifier.width(8.dp)) + data.tagTitle?.let { + NaviTextWidgetized( + textFieldData = it, + modifier = + Modifier.background( + color = + hexToColor(data.properties?.tagProperty?.backgroundColor), + shape = ShapeUtil.getShape(data.properties?.tagProperty?.shape), + ) + .setPadding( + data.properties?.tagProperty?.padding + ?: ComposePadding(start = 10, end = 10, top = 2, bottom = 2) + ), + ) + } + Spacer(modifier = Modifier.weight(1f)) + if (data.showTitleRightIcon == true) { + data.selectionProperties?.let { + NaviImage( + imageFieldData = + if (selectionData?.selectedId == it.isin) it.selectedTitleRightIcon + else it.unselectedTitleRightIcon, + modifier = + Modifier.setPadding( + data.selectionProperties.titleRightIconProperties?.padding + ?: ComposePadding(start = 0, end = 0, top = 0, bottom = 0) + ), + ) + } + } + } + data.subTitle?.let { + NaviTextWidgetized( + textFieldData = it, + modifier = + Modifier.setPadding( + data.properties?.subTitleProperty?.padding + ?: ComposePadding(start = 16, end = 16, top = 0, bottom = 16) + ), + ) + } + data.contentTitle?.let { + NaviTextWidgetized( + textFieldData = it, + modifier = + Modifier.setPadding( + data.properties?.contentTitleProperty?.padding + ?: ComposePadding(start = 16, end = 16, top = 0, bottom = 4) + ), + ) + } + Row( + modifier = + Modifier.fillMaxWidth() + .setPadding( + data.properties?.contentSubtitleProperty?.padding + ?: ComposePadding(start = 16, end = 16, top = 0, bottom = 16) + ), + verticalAlignment = Alignment.CenterVertically, + ) { + data.contentLeftSubtitle?.let { + NaviTextWidgetized( + textFieldData = it, + modifier = + Modifier.then( + if (data.contentLeftSubtitle.text?.length.orZero() > 40) { + Modifier.weight(1f) + } else { + Modifier + } + ), + ) + } + data.contentMiddleImage?.let { NaviImage(imageFieldData = it) } + data.contentRightSubtitle?.let { + NaviTextWidgetized(textFieldData = it, modifier = Modifier.wrapContentWidth()) + } + } + } + } +} diff --git a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/AmcHeaderData.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/AmcHeaderData.kt index 239043ee0a..316586bebf 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/AmcHeaderData.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/AmcHeaderData.kt @@ -46,3 +46,5 @@ data class AmcHeaderExtraData( val scrollOffsetForHeaderStateToggle: Int? = null, val toggleShadow: ((showShadow: Boolean) -> Unit)? = null, ) + +data class SelectionData(val selectedId: String? = null) diff --git a/android/app/src/main/java/com/naviapp/home/dashboard/models/investmentTabWidgetData/common/PaymentCard.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/cards/PaymentCard.kt similarity index 55% rename from android/app/src/main/java/com/naviapp/home/dashboard/models/investmentTabWidgetData/common/PaymentCard.kt rename to android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/cards/PaymentCard.kt index 6257945e8f..432828235a 100644 --- a/android/app/src/main/java/com/naviapp/home/dashboard/models/investmentTabWidgetData/common/PaymentCard.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/cards/PaymentCard.kt @@ -6,6 +6,7 @@ */ import com.google.gson.annotations.SerializedName +import com.navi.amc.common.model.NextCtaResponse import com.navi.base.model.ActionData import com.navi.common.model.GenericBottomSheetData import com.navi.common.model.InvestmentBaseProperty @@ -40,3 +41,30 @@ data class CardProperties( @SerializedName("investmentsIconProperty") val investmentsIconProperty: InvestmentBaseProperty? = null, ) + +data class BottomSheetData( + @SerializedName("title") val title: TextFieldData? = null, + @SerializedName("subtitle") val subtitle: TextFieldData? = null, + @SerializedName("leftIcon") val leftIcon: ImageFieldData? = null, + @SerializedName("rightIcon") val rightIcon: ImageFieldData? = null, + @SerializedName("buttonText", alternate = ["primaryButtonText"]) + val buttonText: TextFieldData? = null, + @SerializedName("secondaryButtonText") val secondaryButtonText: TextFieldData? = null, + @SerializedName("noteData") val noteData: BottomSheetData? = null, + @SerializedName("imageUrl") val imageUrl: ImageFieldData? = null, + @SerializedName("properties") val properties: BottomSheetProperties? = null, + @SerializedName("actionData") val actionData: ActionData? = null, + @SerializedName("nextCtaResponse") val nextCtaResponse: NextCtaResponse? = null, +) + +data class BottomSheetProperties( + @SerializedName("buttonProperty", alternate = ["primaryButtonProperty"]) + val primaryButtonProperty: InvestmentBaseProperty? = null, + @SerializedName("secondaryButtonProperty") + val secondaryButtonProperty: InvestmentBaseProperty? = null, + @SerializedName("subtitleProperty") val subtitleProperty: InvestmentBaseProperty? = null, + @SerializedName("titleProperty") val titleProperty: InvestmentBaseProperty? = null, + @SerializedName("imageProperty") val imageProperty: InvestmentBaseProperty? = null, + @SerializedName("noteProperty") val noteProperty: InvestmentBaseProperty? = null, + @SerializedName("bottomSheetProperty") val bottomSheetProperty: InvestmentBaseProperty? = null, +) diff --git a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/widgets/FtueWithTrackerWidget.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/widgets/FtueWithTrackerWidget.kt new file mode 100644 index 0000000000..bc28661963 --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/widgets/FtueWithTrackerWidget.kt @@ -0,0 +1,81 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.fundbuy.models.widgets + +import com.google.gson.annotations.SerializedName +import com.navi.amc.common.model.GenericComposableWidget +import com.navi.base.model.ActionData +import com.navi.base.model.GenericAnalytics +import com.navi.common.model.InvestmentBaseProperty +import com.navi.naviwidgets.models.response.ImageFieldData +import com.navi.naviwidgets.models.response.TextFieldData + +data class FtueWithTrackerWidget( + @SerializedName("widgetName") override val widgetName: String? = null, + @SerializedName("widgetId") override val widgetId: String? = null, + @SerializedName("widgetData") val widgetData: FtueWithTrackerWidgetData? = null, +) : GenericComposableWidget + +data class FtueWithTrackerWidgetData( + @SerializedName("content") val content: FtueWithTrackerWidgetContent? = null, + @SerializedName("extraData") val extraData: FtueWithTrackerWidgetExtraData? = null, +) + +data class FtueWithTrackerWidgetContent( + @SerializedName("topLogo") val topLogo: ImageFieldData? = null, + @SerializedName("topText") val topText: TextFieldData? = null, + @SerializedName("rolodexItems") val rolodexItems: List? = null, + @SerializedName("chipItems") val chipItems: List? = null, + @SerializedName("actionCardData") val actionCardData: FtueWithTrackerActionCardData? = null, + @SerializedName("properties") val properties: FtueWithTrackerWidgetProperties? = null, +) + +data class FtueWithTrackerActionCardData( + @SerializedName("trackerItems") val trackerItems: List? = null, + @SerializedName("buttonText") val buttonText: TextFieldData? = null, + @SerializedName("footerTexts") val footerTexts: FooterTexts? = null, + @SerializedName("actionData") val actionData: ActionData? = null, + @SerializedName("actionCardProperty") val actionCardProperty: FtueActionCardProperty? = null, +) + +data class IconWithTextCard( + @SerializedName("icon") val icon: ImageFieldData? = null, + @SerializedName("text") val text: TextFieldData? = null, + @SerializedName("actionData") val actionData: ActionData? = null, + @SerializedName("property") val property: InvestmentBaseProperty? = null, +) + +data class FooterTexts( + @SerializedName("leftText") val leftText: TextFieldData? = null, + @SerializedName("rightText") val rightText: TextFieldData? = null, +) + +data class FtueActionCardProperty( + @SerializedName("cardProperty") val cardProperty: InvestmentBaseProperty? = null, + @SerializedName("trackerItemsProperty") + val trackerItemsProperty: InvestmentBaseProperty? = null, + @SerializedName("trackerDividerProperty") + val trackerDividerProperty: InvestmentBaseProperty? = null, + @SerializedName("buttonProperty") val buttonProperty: InvestmentBaseProperty? = null, + @SerializedName("buttonTextProperty") val buttonTextProperty: InvestmentBaseProperty? = null, + @SerializedName("footerTextsProperty") val footerTextsProperty: InvestmentBaseProperty? = null, +) + +data class FtueWithTrackerWidgetProperties( + @SerializedName("cardProperty") val cardProperty: InvestmentBaseProperty? = null, + @SerializedName("topLogoProperty") val topLogoProperty: InvestmentBaseProperty? = null, + @SerializedName("topTextProperty") val topTextProperty: InvestmentBaseProperty? = null, + @SerializedName("rolodexItemProperty") val rolodexItemProperty: InvestmentBaseProperty? = null, + @SerializedName("chipProperty") val chipProperty: InvestmentBaseProperty? = null, + @SerializedName("chipItemProperty") val chipItemProperty: InvestmentBaseProperty? = null, +) + +data class FtueWithTrackerWidgetExtraData( + @SerializedName("extraProperties") val extraProperties: InvestmentBaseProperty? = null, + @SerializedName("metaData") val metaData: GenericAnalytics? = null, +) diff --git a/android/app/src/main/java/com/naviapp/home/dashboard/models/investmentTabWidgetData/RepeatOrderWidget.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/widgets/RepeatOrderWidget.kt similarity index 65% rename from android/app/src/main/java/com/naviapp/home/dashboard/models/investmentTabWidgetData/RepeatOrderWidget.kt rename to android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/widgets/RepeatOrderWidget.kt index 777d44c2f3..c4bd5ebff5 100644 --- a/android/app/src/main/java/com/naviapp/home/dashboard/models/investmentTabWidgetData/RepeatOrderWidget.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/widgets/RepeatOrderWidget.kt @@ -5,7 +5,7 @@ * */ -package com.naviapp.home.dashboard.models.investmentTabWidgetData +package com.navi.amc.fundbuy.models.widgets import PaymentCard import com.google.gson.annotations.SerializedName @@ -32,10 +32,21 @@ data class RepeatOrderWidgetHeader( ) data class RepeatOrderWidgetContent( - @SerializedName("repeatOrderCardList") val repeatOrderCardList: List? = null + @SerializedName("repeatOrderCardList") val repeatOrderCardList: List? = null, + @SerializedName("carouselData") val carouselData: CarouselData? = null, ) data class RepeatOrderWidgetExtraData( @SerializedName("property") val property: InvestmentBaseProperty? = null, + @SerializedName("scrollType") val scrollType: String? = null, @SerializedName("metaData") val metaData: GenericAnalytics? = null, ) + +data class CarouselData( + @SerializedName("carouselHeight") val carouselHeight: Int? = null, + @SerializedName("activeCarouselWidth") val activeCarouselWidth: Int? = null, + @SerializedName("inactiveCarouselWidth") val inactiveCarouselWidth: Int? = null, + @SerializedName("activeCarouselColor") val activeCarouselColor: String? = null, + @SerializedName("inactiveCarouselColor") val inactiveCarouselColor: String? = null, + @SerializedName("carouselProperty") val carouselProperty: InvestmentBaseProperty? = null, +) diff --git a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/widgets/TitleContentRadioWidget.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/widgets/TitleContentRadioWidget.kt new file mode 100644 index 0000000000..76808700b5 --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/widgets/TitleContentRadioWidget.kt @@ -0,0 +1,57 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.fundbuy.models.widgets + +import com.google.gson.annotations.SerializedName +import com.navi.amc.common.model.GenericComposableWidget +import com.navi.base.model.ActionData +import com.navi.common.model.InvestmentBaseProperty +import com.navi.naviwidgets.models.response.ImageFieldData +import com.navi.naviwidgets.models.response.TextFieldData + +data class TitleContentRadioWidget( + @SerializedName("widgetName") override val widgetName: String? = null, + @SerializedName("widgetId") override val widgetId: String? = null, + @SerializedName("widgetData") val widgetData: TitleContentRadioWidgetData? = null, +) : GenericComposableWidget + +data class TitleContentRadioWidgetData( + @SerializedName("title") val title: TextFieldData? = null, + @SerializedName("subTitle", alternate = ["subtitle"]) val subTitle: TextFieldData? = null, + @SerializedName("tagTitle") val tagTitle: TextFieldData? = null, + @SerializedName("showTitleRightIcon") val showTitleRightIcon: Boolean? = null, + @SerializedName("contentTitle") val contentTitle: TextFieldData? = null, + @SerializedName("contentLeftSubtitle") val contentLeftSubtitle: TextFieldData? = null, + @SerializedName("contentRightSubtitle") val contentRightSubtitle: TextFieldData? = null, + @SerializedName("contentMiddleImage") val contentMiddleImage: ImageFieldData? = null, + @SerializedName("properties") val properties: TitleContentRadioWidgetProperties? = null, + @SerializedName("actionData") val actionData: ActionData? = null, + @SerializedName("selectionProperties") val selectionProperties: SelectionProperties? = null, +) + +data class TitleContentRadioWidgetProperties( + @SerializedName("cardProperty") val cardProperty: InvestmentBaseProperty? = null, + @SerializedName("titleProperty") val titleProperty: InvestmentBaseProperty? = null, + @SerializedName("subTitleProperty") val subTitleProperty: InvestmentBaseProperty? = null, + @SerializedName("tagProperty") val tagProperty: InvestmentBaseProperty? = null, + @SerializedName("contentTitleProperty") + val contentTitleProperty: InvestmentBaseProperty? = null, + @SerializedName("contentSubtitleProperty") + val contentSubtitleProperty: InvestmentBaseProperty? = null, +) + +data class SelectionProperties( + @SerializedName("type") val type: String? = null, + @SerializedName("isin") val isin: String? = null, + @SerializedName("isSelected") val isSelected: Boolean? = null, + @SerializedName("selectedTitleRightIcon") val selectedTitleRightIcon: ImageFieldData? = null, + @SerializedName("unselectedTitleRightIcon") + val unselectedTitleRightIcon: ImageFieldData? = null, + @SerializedName("titleRightIconProperties") + val titleRightIconProperties: InvestmentBaseProperty? = null, +) diff --git a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/widgets/TitleSubtitleImageWidget.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/widgets/TitleSubtitleImageWidget.kt index caa2a5818e..c1d63bd94b 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/widgets/TitleSubtitleImageWidget.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/widgets/TitleSubtitleImageWidget.kt @@ -31,7 +31,8 @@ data class TitleSubtitleImageWidgetData( data class TitleSubtitleImageWidgetProperties( @SerializedName("cardProperty") val cardProperty: InvestmentBaseProperty? = null, @SerializedName("titleProperty") val titleProperty: InvestmentBaseProperty? = null, - @SerializedName("subTitleProperty") val subTitleProperty: InvestmentBaseProperty? = null, + @SerializedName("subTitleProperty", alternate = ["subtitleProperty"]) + val subTitleProperty: InvestmentBaseProperty? = null, @SerializedName("imageProperty") val imageProperty: InvestmentBaseProperty? = null, @SerializedName("footerProperty") val footerProperty: InvestmentBaseProperty? = null, ) diff --git a/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/widgets/TitleSubtitleLottieWidget.kt b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/widgets/TitleSubtitleLottieWidget.kt new file mode 100644 index 0000000000..178fa33ecc --- /dev/null +++ b/android/navi-amc/src/main/java/com/navi/amc/fundbuy/models/widgets/TitleSubtitleLottieWidget.kt @@ -0,0 +1,38 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.amc.fundbuy.models.widgets + +import com.google.gson.annotations.SerializedName +import com.navi.amc.common.model.GenericComposableWidget +import com.navi.base.model.ActionData +import com.navi.common.model.InvestmentBaseProperty +import com.navi.naviwidgets.models.LottieFieldData +import com.navi.naviwidgets.models.response.TextFieldData + +data class TitleSubtitleLottieWidget( + @SerializedName("widgetName") override val widgetName: String? = null, + @SerializedName("widgetId") override val widgetId: String? = null, + @SerializedName("widgetData") val widgetData: TitleSubtitleLottieWidgetData? = null, +) : GenericComposableWidget + +data class TitleSubtitleLottieWidgetData( + @SerializedName("title") val title: TextFieldData? = null, + @SerializedName("subTitle", alternate = ["subtitle"]) val subTitle: TextFieldData? = null, + @SerializedName("lottie", alternate = ["icon"]) val lottie: LottieFieldData? = null, + @SerializedName("properties") val properties: TitleSubtitleLottieWidgetProperties? = null, + @SerializedName("actionData") val actionData: ActionData? = null, +) + +data class TitleSubtitleLottieWidgetProperties( + @SerializedName("cardProperty") val cardProperty: InvestmentBaseProperty? = null, + @SerializedName("titleProperty") val titleProperty: InvestmentBaseProperty? = null, + @SerializedName("subTitleProperty", alternate = ["subtitleProperty"]) + val subTitleProperty: InvestmentBaseProperty? = null, + @SerializedName("lottieProperty") val lottieProperty: InvestmentBaseProperty? = null, + @SerializedName("footerProperty") val footerProperty: InvestmentBaseProperty? = null, +) diff --git a/android/navi-amc/src/main/java/com/navi/amc/navigator/NaviAmcDeeplinkNavigator.kt b/android/navi-amc/src/main/java/com/navi/amc/navigator/NaviAmcDeeplinkNavigator.kt index d16973dffc..e612bac2cc 100644 --- a/android/navi-amc/src/main/java/com/navi/amc/navigator/NaviAmcDeeplinkNavigator.kt +++ b/android/navi-amc/src/main/java/com/navi/amc/navigator/NaviAmcDeeplinkNavigator.kt @@ -12,9 +12,11 @@ import android.content.Intent import android.net.Uri import android.os.Bundle import com.navi.amc.common.activity.CheckerActivity +import com.navi.amc.compose.entry.AmcComposeActivity import com.navi.amc.fundbuy.activities.FundBuyActivity import com.navi.amc.kyc.activity.KycActivity import com.navi.amc.kyc.activity.KycOnboardActivity +import com.navi.amc.utils.AmcAnalytics.TRUE import com.navi.amc.utils.getCtaToNavigateToInvestmentTab import com.navi.amc.utils.updateSecondIdentifier import com.navi.base.deeplink.DeepLinkManager @@ -22,7 +24,6 @@ import com.navi.base.model.ActionData import com.navi.base.utils.orFalse import com.navi.base.utils.orZero import com.navi.common.utils.Constants -import com.navi.common.utils.Constants.TRUE import com.navi.common.utils.toCtaData import timber.log.Timber @@ -39,6 +40,7 @@ object NaviAmcDeeplinkNavigator { const val WEB_URL = "webUrl" const val FUND_LANDING = "fund_landing" const val V2_PARAMETER = "v2" + const val FTUE = "ftue" fun navigate( activity: Activity?, @@ -113,6 +115,9 @@ object NaviAmcDeeplinkNavigator { addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) } } + FTUE -> { + Intent(activity, AmcComposeActivity::class.java) + } WEB_URL -> { var url: String? = null ctaData.parameters?.forEach { keyValue -> 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 bd54e0de52..f0eaae5698 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 @@ -9,6 +9,8 @@ package com.navi.amc.network.retrofit import com.navi.amc.common.model.* import com.navi.amc.common.model.cart.CartRequest +import com.navi.amc.compose.common.model.AmcGenericScreenResponse +import com.navi.amc.compose.feature.ftue.model.FundSelectionData import com.navi.amc.digio.AadhaarVerificationData import com.navi.amc.digio.DigioKycPollingResponse import com.navi.amc.digio.DigioKycResponse @@ -599,4 +601,15 @@ interface RetrofitService { @Path("checkoutId") checkoutId: String?, @Path(value = "nextStepCta", encoded = true) nextStepCta: String?, ): Response> + + @GET("/investor/ftue/educate") + suspend fun fetchFtueEducateScreenData(): Response> + + @GET("/investor/ftue/fund-details") + suspend fun fetchFtueFundSelectScreenData(): Response> + + @POST("/investor/ftue/update") + suspend fun postSelectedFund( + @Body fundSelectionData: FundSelectionData + ): Response> } 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 1b99c1d830..8456555d52 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 @@ -240,4 +240,10 @@ object Constant { const val ERROR_RESPONSE = "ERROR_RESPONSE" const val NEW_FLOW_TYPE = "new_flow_type" const val SIP_TYPE = "sip_type" + + const val UPCOMING_SIP_PAYMENT_CARD = "upcomingSipPaymentCard" + const val REPEAT_ORDER = "repeatOrder" + + const val START_SCREEN_NAME = "start_screen_name" + const val TRANSITION_DURATION_IN_MILLIS = 400 }