From 17947ee5aefe44017548f18aad21b92eb2de53c1 Mon Sep 17 00:00:00 2001 From: Hitesh Kumar Date: Thu, 27 Mar 2025 19:08:56 +0530 Subject: [PATCH] NTP-42975 - upi spend analyser (#15356) Co-authored-by: Aparna Vadlamani Co-authored-by: Abhinav Gupta Co-authored-by: Sanjay P --- .../common/handler/PostRenderTaskExecutor.kt | 3 +- .../code/processors/EventTrackerProcessor.kt | 106 ++++-- .../FirebaseRemoteConfigHelper.kt | 1 + .../main/java/com/navi/common/utils/Ext.kt | 12 + .../main/res/xml/default_remote_config.xml | 4 + .../com/navi/design/utils/NaviDrawable.kt | 1 + .../base/viewmodel/MMBaseViewModel.kt | 6 +- .../common/analytics/MMAnalytics.kt | 186 +++++++++- .../provider/AddCategoryDataProviderImpl.kt | 12 +- .../helper/MMConfigResponseHelper.kt | 8 + .../helper/SpendCategorizationHelper.kt | 84 +++-- .../provider/DashboardDataProviderImpl.kt | 56 ++- .../UpiSpendDashboardDataProviderImpl.kt | 217 ++++++++++++ .../data/remote/RemoteDataProviderImpl.kt | 10 + .../upi/UpiSpendLocalDataSyncManagerImpl.kt | 63 ++++ .../OtherCategoriesBottomSheetDataHelper.kt | 2 + .../SpendAnalysisBarGraphSectionHelper.kt | 19 +- .../SpendAnalysisCategoriesSectionHelper.kt | 3 + .../helper/TotalSpendSectionDataHelper.kt | 82 ++++- .../CategoryDetailsDataProviderImpl.kt | 12 +- .../provider/SpendAnalysisDataProviderImpl.kt | 20 +- .../UpiSpendAnalysisDataProviderImpl.kt | 225 ++++++++++++ ...endCategoryDetailsLocalDataProviderImpl.kt | 195 ++++++++++ .../helper/SpendGoalBarGraphSectionHelper.kt | 1 + .../helper/TransactionProviderHelper.kt | 43 ++- .../TransactionDetailsDataProviderImpl.kt | 10 +- .../TransactionHistoryDataProviderImpl.kt | 12 +- ...SpendTransactionDetailsDataProviderImpl.kt | 243 +++++++++++++ .../upi/TxnHistoryUpiSpendDataProviderImpl.kt | 135 +++++++ .../dataprovider/di/DataProviderModule.kt | 44 ++- .../domain/DashboardDataProvider.kt | 3 + .../dataprovider/domain/RemoteDataProvider.kt | 5 + ...piSpendCategoryDetailsLocalDataProvider.kt | 33 ++ .../domain/UpiSpendDashboardDataProvider.kt | 32 ++ .../upi/TxnHistoryUpiSpendDataProvider.kt | 18 + .../upi/UpiSpendAnalysisLocalDataProvider.kt | 29 ++ .../upi/UpiSpendLocalDataSyncManager.kt | 21 ++ .../upi/UpiSpendTransactionDataProvider.kt | 18 + .../dataprovider/utils/DataProviderUtils.kt | 6 +- .../common/datasync/DBSyncExecutor.kt | 17 +- .../common/datasync/UpiSpendDBSyncExecutor.kt | 113 ++++++ .../helper/AllMonthsDataSyncHelper.kt | 17 +- .../datasync/helper/BaseDataSyncHelper.kt | 15 +- .../helper/CurrentMonthDataSyncHelper.kt | 13 +- .../model/TransactionsApiCallStatus.kt | 2 +- .../UpiSpendTransactionsApiCallStatus.kt | 20 ++ .../db/upi/dao/UpiSpendTransactionDao.kt | 68 ++++ .../db/upi/database/UpiSpendDatabase.kt | 19 + .../upi/entity/UpiSpendTransactionEntity.kt | 32 ++ .../common/helper/BarGraphHelper.kt | 2 + .../helper/UpiSpendAnalysisWidgetHelper.kt | 83 +++++ .../helper/UpiSpendTransactionsDataHelper.kt | 58 +++ .../repository/ImageRepository.kt | 8 + .../common/manager/MMLibManager.kt | 9 + .../common/model/DailySpendBottomSheetData.kt | 1 + .../moneymanager/common/model/MMToastData.kt | 10 + .../common/model/SpendCategorizationLoaded.kt | 16 +- .../moneymanager/common/model/Transaction.kt | 1 + .../DashboardScreenBottomSheets.kt | 12 + .../model/database/UpiTransactionDetails.kt | 19 + .../database/UpiTransactionSummaryData.kt | 36 ++ .../model/sectionHeader/JourneySource.kt | 13 + .../upi/UpiTxnHistorySpendAnalysisData.kt | 30 ++ .../common/network/di/NetworkModule.kt | 9 + .../network/model/TransactionResponse.kt | 2 +- .../network/model/UpiSpendTransactionData.kt | 22 ++ .../model/UpiSpendTransactionResponse.kt | 14 + .../common/network/service/RetrofitService.kt | 7 + .../common/ui/composable/MMProgressBar.kt | 3 +- .../common/ui/composable/MMSearchAndFilter.kt | 3 +- .../ui/composable/barGraph/MMBarGraph.kt | 29 +- .../common/ui/composable/base/MMTextField.kt | 4 +- .../bottomSheet/AverageInfoBottomSheet.kt | 35 +- .../bottomSheet/MonthSelectionBottomSheet.kt | 54 +-- .../CategoryTotalSpendSection.kt | 118 ++++--- .../SpendCategorizationSection.kt | 31 +- .../spendCategoriztion/SpendCategory.kt | 209 ++++++----- .../ui/composable/transaction/Transaction.kt | 5 +- .../transaction/TransactionListSectionUI.kt | 13 +- ...piTransactionHistorySpendAnalyserWidget.kt | 333 ++++++++++++++++++ .../moneymanager/common/utils/Constants.kt | 13 + .../navi/moneymanager/common/utils/Utils.kt | 40 ++- .../entry/ui/activity/MMActivity.kt | 32 +- .../entry/viewmodel/MMSharedViewModel.kt | 29 +- .../model/CategoryDetailsScreenData.kt | 2 + .../repo/CategoryDetailsRepository.kt | 74 +++- .../ui/CategoryDetailsScaffoldRenderer.kt | 37 +- .../ui/CategoryDetailsScreen.kt | 57 +-- .../ui/CategorySelectionBottomSheetContent.kt | 12 +- .../ui/UncategorizedInfoBottomSheetContent.kt | 4 +- .../viewmodel/CategoryDetailsVM.kt | 31 +- .../dashboard/model/DashboardData.kt | 13 + .../model/DashboardScreenUiEffect.kt | 2 + .../dashboard/model/DashboardScreenUiEvent.kt | 7 + .../reducer/DashboardScreenReducer.kt | 16 + .../repo/DashboardScreenRepository.kt | 95 ++++- .../ui/DashBoardFinarkeinErrorBottomSheet.kt | 27 +- .../ui/DashboardContainerAnimatingEffect.kt | 21 +- .../dashboard/ui/DashboardFeedbackSection.kt | 112 ++++++ .../DashboardOnboardingBottomSheetContent.kt | 31 +- .../ui/DashboardRecentTransactionsSection.kt | 7 + .../dashboard/ui/DashboardScaffoldRenderer.kt | 12 +- .../dashboard/ui/DashboardScreen.kt | 63 +++- .../dashboard/ui/DashboardSections.kt | 58 +-- .../dashboard/ui/FeedbackBottomSheet.kt | 138 ++++++++ .../dashboard/ui/bankSection/BankSection.kt | 10 +- .../ui/bankSection/BankStatusSection.kt | 8 +- .../usecase/RefreshAndSyncDataUseCase.kt | 19 +- .../dashboard/viewmodel/DashboardViewModel.kt | 165 +++++++-- .../postonboard/help/ui/HelpBottomSheet.kt | 76 +--- .../bottomsheet/CategoryCommonBottomSheet.kt | 229 ++---------- .../viewmodel/CategoryBottomSheetViewModel.kt | 10 + .../model/SpendAnalysisScreenData.kt | 4 +- .../repo/SpendAnalysisRepository.kt | 65 +++- .../ui/DailySpendBottomSheetContent.kt | 23 +- .../ui/OtherCategoriesBottomSheet.kt | 8 +- .../ui/SpendAnalysisScaffoldRenderer.kt | 13 +- .../spendanalysis/ui/SpendAnalysisScreen.kt | 68 +++- .../spendanalysis/ui/TotalSpendSection.kt | 72 ++-- .../viewmodel/SpendAnalysisVM.kt | 36 +- .../spendgoal/helper/SpendGoalHelper.kt | 28 +- .../ui/SpendGoalBottomSheetContentHandler.kt | 10 +- .../spendgoal/ui/SpendGoalScaffoldRenderer.kt | 3 + .../spendgoal/ui/SpendGoalScreen.kt | 6 +- .../spendgoal/ui/SpendGoalSections.kt | 94 ++--- .../ui/bottomsheet/ProgressBarBottomSheet.kt | 12 +- .../ui/composable/SpendGoalGraphSection.kt | 3 + .../spendgoal/viewmodel/SpendGoalViewModel.kt | 12 + .../model/TransactionDetailsScreenData.kt | 3 +- .../TransactionDetailsScreenRepository.kt | 26 +- .../ui/TransactionCategorySection.kt | 50 +-- .../ui/TransactionDetailsScaffoldRenderer.kt | 8 +- .../ui/TransactionDetailsScreen.kt | 9 +- .../viewModel/TransactionDetailsViewModel.kt | 17 +- .../TransactionHistoryBottomSheetContent.kt | 11 +- .../ui/TransactionHistoryContent.kt | 14 +- .../ui/TransactionHistoryScaffold.kt | 12 +- .../ui/TransactionHistoryScreen.kt | 8 +- .../viewmodel/TransactionHistoryViewModel.kt | 26 +- .../AccountLinkingStatusViewModel.kt | 15 +- .../launcher/ui/ConsentRevokedBottomSheet.kt | 12 +- .../launcher/ui/GenericErrorBottomSheet.kt | 23 +- .../launcher/ui/LauncherErrorScreen.kt | 5 +- .../preonboard/launcher/ui/LauncherScreen.kt | 7 +- .../launcher/viewmodel/LauncherViewModel.kt | 17 + .../valueprop/ui/ValuePropScreen.kt | 10 +- .../valueprop/viewmodel/ValuePropViewModel.kt | 10 + .../src/main/res/values/strings.xml | 17 + android/navi-pay/build.gradle | 1 + .../navi/pay/tstore/list/db/dao/OrderDao.kt | 3 + .../model/view/OrderHistoryBottomsheetType.kt | 17 + ...rderHistoryScreenBottomSheetStateHolder.kt | 13 + .../UpiSpendAnalyzerInfoBottomsheetData.kt | 13 + .../tstore/list/repository/OrderRepository.kt | 4 + .../list/ui/OrderHistoryBottomSheetContent.kt | 111 ++++++ .../pay/tstore/list/ui/OrderHistoryScreen.kt | 174 ++++++++- .../list/viewmodel/OrderHistoryViewModel.kt | 120 ++++++- .../com/navi/pay/utils/NaviPayConstants.kt | 2 + .../navi-pay/src/main/res/values/strings.xml | 2 + .../extensions/ComposeWidgetExt.kt | 4 +- 160 files changed, 4993 insertions(+), 1163 deletions(-) create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/dashboard/provider/UpiSpendDashboardDataProviderImpl.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/remote/upi/UpiSpendLocalDataSyncManagerImpl.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/provider/UpiSpendAnalysisDataProviderImpl.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/provider/UpiSpendCategoryDetailsLocalDataProviderImpl.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/transaction/provider/upi/UpiSpendTransactionDetailsDataProviderImpl.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/upi/TxnHistoryUpiSpendDataProviderImpl.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/UpiSpendCategoryDetailsLocalDataProvider.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/UpiSpendDashboardDataProvider.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/upi/TxnHistoryUpiSpendDataProvider.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/upi/UpiSpendAnalysisLocalDataProvider.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/upi/UpiSpendLocalDataSyncManager.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/upi/UpiSpendTransactionDataProvider.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/UpiSpendDBSyncExecutor.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/model/UpiSpendTransactionsApiCallStatus.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/db/upi/dao/UpiSpendTransactionDao.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/db/upi/database/UpiSpendDatabase.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/db/upi/entity/UpiSpendTransactionEntity.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/helper/UpiSpendAnalysisWidgetHelper.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/helper/UpiSpendTransactionsDataHelper.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/MMToastData.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/database/UpiTransactionDetails.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/database/UpiTransactionSummaryData.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/sectionHeader/JourneySource.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/upi/UpiTxnHistorySpendAnalysisData.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/network/model/UpiSpendTransactionData.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/network/model/UpiSpendTransactionResponse.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/upi/UpiTransactionHistorySpendAnalyserWidget.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardFeedbackSection.kt create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/FeedbackBottomSheet.kt create mode 100644 android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/model/view/OrderHistoryBottomsheetType.kt create mode 100644 android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/model/view/OrderHistoryScreenBottomSheetStateHolder.kt create mode 100644 android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/model/view/UpiSpendAnalyzerInfoBottomsheetData.kt diff --git a/android/app/src/main/java/com/naviapp/home/common/handler/PostRenderTaskExecutor.kt b/android/app/src/main/java/com/naviapp/home/common/handler/PostRenderTaskExecutor.kt index 295a07daa5..5810c35348 100644 --- a/android/app/src/main/java/com/naviapp/home/common/handler/PostRenderTaskExecutor.kt +++ b/android/app/src/main/java/com/naviapp/home/common/handler/PostRenderTaskExecutor.kt @@ -1,6 +1,6 @@ /* * - * * Copyright © 2024 by Navi Technologies Limited + * * Copyright © 2024-2025 by Navi Technologies Limited * * All rights reserved. Strictly confidential * */ @@ -28,5 +28,6 @@ class PostRenderTaskExecutor @Inject constructor(private val mmLibManager: Lazy< if (PreferenceManager.getBooleanPreference(key = MM_IS_USER_ONBOARDED)) { mmLibManager.get().initMoneyManagerDB() } + mmLibManager.get().initUpiSpendAnalyserDB() } } diff --git a/android/navi-code/src/main/kotlin/com/navi/code/processors/EventTrackerProcessor.kt b/android/navi-code/src/main/kotlin/com/navi/code/processors/EventTrackerProcessor.kt index dde2fdbe26..98503e6270 100644 --- a/android/navi-code/src/main/kotlin/com/navi/code/processors/EventTrackerProcessor.kt +++ b/android/navi-code/src/main/kotlin/com/navi/code/processors/EventTrackerProcessor.kt @@ -19,12 +19,15 @@ import com.google.devtools.ksp.symbol.KSFunctionDeclaration import com.google.devtools.ksp.validate import com.navi.code.annotations.AutoGenerate import com.navi.code.annotations.EventName +import com.navi.code.utils.generateOverrideMethodWithDefaultSuperCall import com.navi.code.utils.isDefaultKotlinFunction import com.navi.code.utils.toSnakeCase import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.ksp.toClassName import java.io.OutputStreamWriter @@ -74,9 +77,31 @@ class EventTrackerProcessor( FileSpec.builder(packageName, generatedClassName) .addImport("com.navi.analytics.utils", "NaviTrackEvent") - val classBuilder = - TypeSpec.objectBuilder(generatedClassName).addModifiers(KModifier.INTERNAL) + val constructor = + FunSpec.constructorBuilder() + .addParameter( + ParameterSpec.builder( + "parameters", + Map::class.parameterizedBy(String::class, String::class), + ) + .defaultValue("emptyMap()") + .build() + ) + .build() + val classBuilder = + TypeSpec.classBuilder(generatedClassName) + .primaryConstructor(constructor) + .addSuperinterface(classDeclaration.toClassName()) + .addProperty( + PropertySpec.builder( + "parameters", + Map::class.parameterizedBy(String::class, String::class), + ) + .initializer("parameters") + .addModifiers(KModifier.PRIVATE) + .build() + ) classDeclaration .getAllFunctions() .filterNot { it.isDefaultKotlinFunction() } @@ -97,44 +122,51 @@ class EventTrackerProcessor( private fun generateMethod(function: KSFunctionDeclaration): FunSpec { val methodName = function.simpleName.asString() + return if (function.isAbstract.not()) { + function.generateOverrideMethodWithDefaultSuperCall() + } else { - val parameters = function.parameters + val parameters = function.parameters - val eventName = - function.annotations - .find { it.shortName.asString() == EventName::class.simpleName } - ?.arguments - ?.firstOrNull() - ?.value as? String - ?: throw IllegalArgumentException("Event Name is missing for method $methodName.") + val eventName = + function.annotations + .find { it.shortName.asString() == EventName::class.simpleName } + ?.arguments + ?.firstOrNull() + ?.value as? String + ?: throw IllegalArgumentException( + "Event Name is missing for method $methodName." + ) - if (eventName.isEmpty()) { - throw IllegalArgumentException("Event Name is missing for method $methodName.") + if (eventName.isEmpty()) { + throw IllegalArgumentException("Event Name is missing for method $methodName.") + } + + val paramList = + parameters.map { param -> + val paramName = + param.name?.asString() + ?: throw IllegalArgumentException( + "Parameter name is missing for method $methodName." + ) + val paramType = param.type.resolve().toClassName() + ParameterSpec.builder(paramName, paramType).build() + } + + val paramEntries = + parameters.joinToString(", ") { param -> + val paramName = param.name?.asString() ?: "param" + "\"${paramName.toSnakeCase()}\" to $paramName.toString()" + } + + return FunSpec.builder(methodName) + .addModifiers(KModifier.OVERRIDE) + .addParameters(paramList) + .addStatement( + "NaviTrackEvent.trackEventOnClickStream(eventName = %S, eventValues = parameters.plus(mapOf($paramEntries)))", + eventName, + ) + .build() } - - val paramList = - parameters.map { param -> - val paramName = - param.name?.asString() - ?: throw IllegalArgumentException( - "Parameter name is missing for method $methodName." - ) - val paramType = param.type.resolve().toClassName() - ParameterSpec.builder(paramName, paramType).build() - } - - val paramEntries = - parameters.joinToString(", ") { param -> - val paramName = param.name?.asString() ?: "param" - "\"${paramName.toSnakeCase()}\" to $paramName.toString()" - } - - return FunSpec.builder(methodName) - .addParameters(paramList) - .addStatement( - "NaviTrackEvent.trackEventOnClickStream(eventName = %S, eventValues = mapOf($paramEntries))", - eventName, - ) - .build() } } diff --git a/android/navi-common/src/main/java/com/navi/common/firebaseremoteconfig/FirebaseRemoteConfigHelper.kt b/android/navi-common/src/main/java/com/navi/common/firebaseremoteconfig/FirebaseRemoteConfigHelper.kt index cf8e4da58c..9540b420db 100644 --- a/android/navi-common/src/main/java/com/navi/common/firebaseremoteconfig/FirebaseRemoteConfigHelper.kt +++ b/android/navi-common/src/main/java/com/navi/common/firebaseremoteconfig/FirebaseRemoteConfigHelper.kt @@ -140,6 +140,7 @@ object FirebaseRemoteConfigHelper { "NAVI_PAY_SEND_MONEY_PRE_VALIDATION_ENABLED" const val NAVI_PAY_AUTO_READ_OTP_DISABLED = "NAVI_PAY_AUTO_READ_OTP_DISABLED" const val NAVI_PAY_SET_AS_WALLPAPER_ENABLED = "NAVI_PAY_SET_AS_WALLPAPER_ENABLED" + const val UPI_SPEND_CROSS_VARIANT_TIMEOUT_MILLIS = "UPI_SPEND_CROSS_VARIANT_TIMEOUT_MILLIS" // COMMON const val LITMUS_EXPERIMENTS_CACHE_DURATION_IN_MILLIS = diff --git a/android/navi-common/src/main/java/com/navi/common/utils/Ext.kt b/android/navi-common/src/main/java/com/navi/common/utils/Ext.kt index 428423bba4..5d0cde71ef 100644 --- a/android/navi-common/src/main/java/com/navi/common/utils/Ext.kt +++ b/android/navi-common/src/main/java/com/navi/common/utils/Ext.kt @@ -713,3 +713,15 @@ fun Map.filterLiteralNullValues(): Map { } fun String.endsWithAny(list: List) = list.any { endsWith(it) } + +@Composable +fun Modifier.conditional( + condition: Boolean, + modifier: @Composable Modifier.() -> Modifier, +): Modifier { + return if (condition) { + modifier(this) + } else { + this + } +} diff --git a/android/navi-common/src/main/res/xml/default_remote_config.xml b/android/navi-common/src/main/res/xml/default_remote_config.xml index bd10adcc8f..d53c576bc9 100644 --- a/android/navi-common/src/main/res/xml/default_remote_config.xml +++ b/android/navi-common/src/main/res/xml/default_remote_config.xml @@ -681,4 +681,8 @@ HP_IAN_FETCH_DISABLED false + + UPI_SPEND_CROSS_VARIANT_TIMEOUT_MILLIS + 1296000000 + \ No newline at end of file diff --git a/android/navi-design/src/main/java/com/navi/design/utils/NaviDrawable.kt b/android/navi-design/src/main/java/com/navi/design/utils/NaviDrawable.kt index a2cf198ba2..c93c3ffbb5 100644 --- a/android/navi-design/src/main/java/com/navi/design/utils/NaviDrawable.kt +++ b/android/navi-design/src/main/java/com/navi/design/utils/NaviDrawable.kt @@ -211,4 +211,5 @@ data class BackgroundDrawableData( @SerializedName("horizontalArrangement", alternate = ["arrangement"]) val arrangement: String? = null, @SerializedName("verticalAlignment", alternate = ["alignment"]) val alignment: String? = null, + @SerializedName("peakAspectRatio") val peakAspectRatio: Float? = null, ) : Parcelable diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/base/viewmodel/MMBaseViewModel.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/base/viewmodel/MMBaseViewModel.kt index 2b54cf11f6..fe52ae931d 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/base/viewmodel/MMBaseViewModel.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/base/viewmodel/MMBaseViewModel.kt @@ -24,10 +24,12 @@ open class MMBaseViewModel( reducer: BaseReducer, ) : BaseMviViewModel(initialState, reducer) { + private val mviEventTracker by lazy { MviEventTrackerImpl() } + override fun setEffect(dispatcher: CoroutineContext?, effect: () -> Effect) { super.setEffect(dispatcher = dispatcher, effect = { effect() }) if (isMviEventLoggingEnabled()) { - MviEventTrackerImpl.mviSetEffect( + mviEventTracker.mviSetEffect( viewmodel = this::class.simpleName.orEmpty(), effect = effect().javaClass.simpleName.orEmpty(), ) @@ -37,7 +39,7 @@ open class MMBaseViewModel( override fun sendEvent(event: Event) { super.sendEvent(event) if (isMviEventLoggingEnabled()) { - MviEventTrackerImpl.mviSendEvent( + mviEventTracker.mviSendEvent( viewmodel = this::class.simpleName.orEmpty(), event = event::class.simpleName.orEmpty(), ) diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/analytics/MMAnalytics.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/analytics/MMAnalytics.kt index 98c0b26f3a..05ea6dcaa5 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/analytics/MMAnalytics.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/analytics/MMAnalytics.kt @@ -242,10 +242,42 @@ interface AddCategoryEventTracker { @EventName("mm_dev_add_category_confirm_footer_button_clicked") fun addCategoryConfirmFooterButtonClicked(selectedCategory: String) + + @EventName("mm_add_category_bottom_sheet_appeared") + fun onAddCategoryBottomSheetAppeared(screenName: String) + + @EventName("mm_add_category_bottom_sheet_disappeared") + fun onAddCategoryBottomSheetDisappeared(screenName: String) + + @EventName("mm_categorise_similar_transactions_bottom_sheet_appeared") + fun onCategoriseSimilarTransactionsBottomSheetAppeared(screenName: String) + + @EventName("mm_categorise_similar_transactions_bottom_sheet_disappeared") + fun onCategoriseSimilarTransactionsBottomSheetDisappeared(screenName: String) + + @EventName("mm_transaction_category_selection_applied") + fun onTransactionCategorySelectionApplied( + previousCategory: String, + newCategory: String, + screenName: String, + ) + + @EventName("mm_categorise_similar_transaction_skipped") + fun onCategoriseSimilarTransactionSkipped(category: String, screenName: String) + + @EventName("mm_categorise_similar_transaction_confirmed") + fun onCategoriseSimilarTransactionConfirmed( + isAutoCategoriseChecked: Boolean, + allTransactionsSelected: Boolean, + numberOfTransactionsSelected: Int, + category: String, + screenName: String, + ) } @AutoGenerate -interface ValuePropEventTracker { +interface ValuePropEventTracker : + FinarkeinBottomSheetEventTracker, GenericErrorBottomSheetEventTracker { @EventName("mm_value_prop_screen_landed") fun onValuePropScreenLanded() @@ -271,7 +303,12 @@ interface ValuePropEventTracker { } @AutoGenerate -interface DashboardEventTracker { +interface DashboardEventTracker : + HelpBottomSheetEventTracker, + MonthlySelectionBottomSheetEventTracker, + FinarkeinBottomSheetEventTracker, + ProgressBarBottomSheetEventTracker, + GenericErrorBottomSheetEventTracker { @EventName("mm_dashboard_screen_landed") fun onDashboardScreenLanded() @@ -292,8 +329,6 @@ interface DashboardEventTracker { @EventName("mm_dashboard_category_viewed") fun onDashboardCategoryViewed(categoryRank: Int, categoryType: String) - @EventName("mm_dashboard_total_spend_clicked") fun onDashboardTotalSpendClicked() - @EventName("mm_dashbaord_category_clicked") fun onDashboardCategoryClicked(categoryRank: Int, categoryType: String) @@ -420,6 +455,14 @@ interface DashboardEventTracker { @EventName("mm_dashboard_animation_started") fun onDashboardAnimationStart(isUserOnboarded: Boolean) + + @EventName("mm_dashboard_feedback_bottom_sheet_appeared") + fun onDashboardFeedbackBottomSheetAppeared() + + @EventName("mm_dashboard_feedback") fun onDashboardFeedback(message: String) + + @EventName("mm_dashboard_feedback_bottom_sheet_disappeared") + fun onDashboardFeedbackBottomSheetDisAppeared() } @AutoGenerate @@ -430,7 +473,12 @@ interface LauncherEventTracker { } @AutoGenerate -interface SpendAnalysisEventTracker { +interface SpendAnalysisEventTracker : + HelpBottomSheetEventTracker, + AverageInfoBottomSheetEventTracker, + MMBarGraphEventTracker, + MonthlySelectionBottomSheetEventTracker, + ProgressBarBottomSheetEventTracker { @EventName("mm_spend_analysis_screen_landed") fun onSpendAnalysisScreenLanded(monthIndex: Int, numberOfBanks: Int) @@ -532,10 +580,25 @@ interface SpendAnalysisEventTracker { @EventName("mm_spend_analysis_daily_spend_bottom_sheet_view_transactions_clicked") fun onSpendAnalysisDailySpendBottomSheetViewTransactionsClicked() + + @EventName("mm_spend_goal_entry_point_clicked") + fun onSpendGoalEntryPointClicked(screenName: String, type: String) + + @EventName("mm_dashboard_total_spend_clicked") fun onDashboardTotalSpendClicked() + + @EventName("mm_category_clicked") + fun onCategoryClicked(categoryRank: Int, category: String, screenName: String) + + @EventName("mm_category_viewed") + fun onCategoryViewed(categoryRank: Int, category: String, screenName: String) } @AutoGenerate -interface CategoryDetailsEventTracker { +interface CategoryDetailsEventTracker : + HelpBottomSheetEventTracker, + AverageInfoBottomSheetEventTracker, + MMBarGraphEventTracker, + MonthlySelectionBottomSheetEventTracker { @EventName("mm_category_details_screen_landed") fun onCategoryDetailsScreenLanded(category: String, monthIndex: Int, numberOfBanks: Int) @@ -651,7 +714,7 @@ interface CategoryDetailsEventTracker { } @AutoGenerate -interface TransactionHistoryEventTracker { +interface TransactionHistoryEventTracker : HelpBottomSheetEventTracker { @EventName("mm_transaction_history_screen_landed") fun onTransactionHistoryScreenLanded() @EventName("mm_transaction_history_screen_exit") fun onTransactionHistoryScreenExit() @@ -712,7 +775,7 @@ interface TransactionHistoryEventTracker { } @AutoGenerate -interface TransactionDetailsEventTracker { +interface TransactionDetailsEventTracker : HelpBottomSheetEventTracker { @EventName("mm_transaction_details_screen_landed") fun onTransactionDetailsScreenLanded() @EventName("mm_transaction_details_screen_exit") fun onTransactionDetailsScreenExit() @@ -773,7 +836,11 @@ interface LauncherErrorEventTracker { } @AutoGenerate -interface SpendGoalEventTracker { +interface SpendGoalEventTracker : + HelpBottomSheetEventTracker, + AverageInfoBottomSheetEventTracker, + MMBarGraphEventTracker, + GenericErrorBottomSheetEventTracker { @EventName("mm_spend_goal_screen_landed") fun onSpendGoalScreenLanded() @EventName("mm_spend_goal_screen_exit") fun onSpendGoalScreenExit() @@ -809,6 +876,17 @@ interface SpendGoalEventTracker { @EventName("mm_spend_goal_delete_goal_error") fun onSpendGoalDeleteGoalError(errorMessage: String, goalId: String) + @EventName("mm_spend_goal_screen_average_info_bottom_sheet_appeared") + fun onSpendGoalScreenAverageInfoBottomSheetAppeared() + + @EventName("mm_spend_goal_screen_average_info_bottom_sheet_disappeared") + fun onSpendGoalScreenAverageInfoBottomSheetDisappeared() + + @EventName("mm_spend_goal_screen_graph_viewed") fun onSpendGoalScreenGraphViewed() + + @EventName("mm_spend_goal_screen_graph_bar_clicked") + fun onSpendGoalScreenGraphBarClicked(monthIndex: Int) + companion object { const val SPEND_GOAL_NO_GOAL_CARD = "no_goal_card" const val SPEND_GOAL_SET_GOAL_CARD = "set_goal_card" @@ -817,3 +895,93 @@ interface SpendGoalEventTracker { const val SPEND_GOAL_PROGRESS_BAR_UPDATE_GOAL = "progress_bar_update_goal" } } + +@AutoGenerate +interface HelpBottomSheetEventTracker { + @EventName("mm_help_bottom_sheet_appeared") fun onHelpBottomSheetAppeared(screenName: String) + + @EventName("mm_help_bottom_sheet_disappeared") + fun onHelpBottomSheetDisappeared(screenName: String) + + @EventName("mm_help_bottom_sheet_faq_clicked") + fun onHelpBottomSheetFaqClicked(screenName: String) + + @EventName("mm_help_bottom_sheet_manage_consent_clicked") + fun onHelpBottomSheetManageConsentClicked(screenName: String) +} + +@AutoGenerate +interface AverageInfoBottomSheetEventTracker { + @EventName("mm_average_info_bottom_sheet_appeared") + fun onAverageInfoBottomSheetAppeared(screenName: String) + + @EventName("mm_average_info_bottom_sheet_disappeared") + fun onAverageInfoBottomSheetDisappeared(screenName: String) +} + +@AutoGenerate +interface MMBarGraphEventTracker { + @EventName("mm_bar_graph_viewed") fun onBarGraphViewed(screenName: String) + + @EventName("mm_bar_graph_clicked") fun onBarGraphClicked(monthIndex: Int, screenName: String) +} + +@AutoGenerate +interface MonthlySelectionBottomSheetEventTracker { + @EventName("mm_monthly_selection_bottom_sheet_appeared") + fun onMonthlySelectionBottomSheetAppeared(screenName: String) + + @EventName("mm_monthly_selection_bottom_sheet_disappeared") + fun onMonthlySelectionBottomSheetDisappeared(screenName: String) + + @EventName("mm_month_selection_applied") + fun onMonthSelectionApplied(monthIndex: Int, screenName: String) +} + +@AutoGenerate +interface FinarkeinBottomSheetEventTracker { + @EventName("mm_finarkein_bottom_sheet_appeared") + fun onFinarkeinBottomSheetAppeared(screenName: String) + + @EventName("mm_finarkein_bottom_sheet_disappeared") + fun onFinarkeinBottomSheetDisappeared(screenName: String) +} + +@AutoGenerate +interface ProgressBarBottomSheetEventTracker { + @EventName("mm_progress_bar_bottom_sheet_appeared") + fun onProgressBarBottomSheetAppeared(screenName: String) + + @EventName("mm_progress_bar_bottom_sheet_disappeared") + fun onProgressBarBottomSheetDisappeared(screenName: String) +} + +@AutoGenerate +interface GenericErrorBottomSheetEventTracker { + @EventName("mm_generic_error_bottom_sheet_appeared") + fun onGenericErrorBottomSheetAppeared(screenName: String) + + @EventName("mm_generic_error_bottom_sheet_disappeared") + fun onGenericErrorBottomSheetDisappeared(screenName: String) +} + +@AutoGenerate +interface UpiTransactionHistorySpendAnalyserEventTracker { + + @EventName("mm_UpiTransactionHistorySpendAnalyserWidget_visible") + fun onUpiTransactionHistorySpendAnalyserWidgetVisible(variant: String) + + @EventName("mm_UpiTransactionHistorySpendAnalyserWidget_clicked") + fun onUpiTransactionHistorySpendAnalyserWidgetClicked(variant: String) + + @EventName("mm_MonthlyView_InfoIcon_Clicked") fun onMonthlyViewInfoIconClicked() + + @EventName("mm_UpiSpendAnalyserWidget_CrossIcon_Clicked") + fun onUpiSpendAnalyserWidgetCrossIconClicked() + + @EventName("mm_UpiSpendAnalyzerInfo_BottomSheet_Appeared") + fun onUpiSpendAnalyzerInfoBottomSheetAppeared() + + @EventName("mm_UpiSpendAnalyzerInfo_BottomSheet_Disappeared") + fun onUpiSpendAnalyzerInfoBottomSheetDisappeared() +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/addcategory/provider/AddCategoryDataProviderImpl.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/addcategory/provider/AddCategoryDataProviderImpl.kt index ca1da987f8..533d62c2f8 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/addcategory/provider/AddCategoryDataProviderImpl.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/addcategory/provider/AddCategoryDataProviderImpl.kt @@ -16,6 +16,7 @@ import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQueryFirst import com.navi.moneymanager.common.db.database.MMDatabase import com.navi.moneymanager.common.utils.Constants +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE import com.navi.moneymanager.common.utils.calculateTimestamps import com.navi.moneymanager.postonboard.monthlysummary.model.AddCategoryBottomSheetData import com.navi.moneymanager.postonboard.monthlysummary.model.AddCategoryContentData @@ -34,6 +35,13 @@ constructor( private val similarTransactionProviderHelper: SimilarTransactionProviderHelper, private val mmConfigResponseHelper: MMConfigResponseHelper, ) : AddCategoryDataProvider { + + private val addCategoryEventTracker by lazy { + AddCategoryEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + override suspend fun fetchAddCategoryBottomSheetData( transactionId: String ): Flow = flow { @@ -78,7 +86,7 @@ constructor( similarTransactionsCount == 1 } - AddCategoryEventTrackerImpl.addCategoryBottomSheetFetchDataCalled( + addCategoryEventTracker.addCategoryBottomSheetFetchDataCalled( hasOnlyOneSimilarTransaction = hasOnlyOneSimilarTransaction.toString(), counterPartyName = transaction.counterPartyName.orEmpty(), transactionType = transaction.type.orEmpty(), @@ -147,7 +155,7 @@ constructor( ) val mmConfig = mmConfigResponseHelper.getMMConfig() - AddCategoryEventTrackerImpl.similarTransactionsBottomSheetFetchDataCalled( + addCategoryEventTracker.similarTransactionsBottomSheetFetchDataCalled( similarTransactionListSize = similarTransactions.size.toString(), counterPartyName = transaction.counterPartyName.orEmpty(), transactionType = transactionType, diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/dashboard/helper/MMConfigResponseHelper.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/dashboard/helper/MMConfigResponseHelper.kt index 0166b9e854..e3a57058c2 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/dashboard/helper/MMConfigResponseHelper.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/dashboard/helper/MMConfigResponseHelper.kt @@ -10,6 +10,7 @@ package com.navi.moneymanager.common.dataprovider.data.dashboard.helper import com.google.gson.Gson import com.navi.base.cache.model.NaviCacheEntity import com.navi.base.cache.repository.NaviCacheRepository +import com.navi.moneymanager.common.model.sectionHeader.MMJourneySource import com.navi.moneymanager.common.network.di.MoneyManagerGsonDeserializer import com.navi.moneymanager.common.network.di.MoneyManagerGsonSerializer import com.navi.moneymanager.common.network.model.MMConfigResponse @@ -32,6 +33,13 @@ constructor( private var mmConfigResponse: MMConfigResponse? = null + var journeySource: String = MMJourneySource.ACCOUNT_AGGREGATOR.name + private set + + fun setJourneySource(source: String) { + journeySource = source + } + suspend fun saveMMConfigToDB(data: MMConfigResponse) { val dataJson = gsonSerializer.toJson(data) val cacheEntity = diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/dashboard/helper/SpendCategorizationHelper.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/dashboard/helper/SpendCategorizationHelper.kt index 994213a66a..a20c8ffb87 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/dashboard/helper/SpendCategorizationHelper.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/dashboard/helper/SpendCategorizationHelper.kt @@ -29,12 +29,15 @@ import com.navi.moneymanager.common.model.SelfTransferCategoryData import com.navi.moneymanager.common.model.SpendCategorizationState import com.navi.moneymanager.common.model.SpendCategoryItemData import com.navi.moneymanager.common.model.TotalSpends +import com.navi.moneymanager.common.model.sectionHeader.MMJourneySource import com.navi.moneymanager.common.model.sectionHeader.SectionHeaderData +import com.navi.moneymanager.common.network.model.CategoryItemData import com.navi.moneymanager.common.network.model.MMConfigResponse import com.navi.moneymanager.common.ui.theme.color.MMColor.categoriesProgressBarColors import com.navi.moneymanager.common.utils.Constants import com.navi.moneymanager.common.utils.Constants.SELF_TRANSFER -import com.navi.moneymanager.common.utils.getSpendGoalProgress +import com.navi.moneymanager.common.utils.Constants.UNCATEGORIZED +import com.navi.moneymanager.common.utils.getSpendGoalState import com.navi.naviwidgets.utils.NaviWidgetIconUtils.DOWN_ARROW_BLACK_16 import com.navi.naviwidgets.utils.NaviWidgetIconUtils.HOME_PAGE_UPI_SECTION_RIGHT_ARROW_ICON import com.navi.naviwidgets.utils.NaviWidgetLottieUtils.TUK_TUK_LOTTIE @@ -46,12 +49,14 @@ class SpendCategorizationHelper constructor(@ApplicationContext private val context: Context) { fun getSpendAnalysisEmptyState( + isSpendGoalEnabled: Boolean, categorySummary: List, mmConfig: MMConfigResponse?, month: Int, year: Int, isTotalSyncCompleted: Boolean, spendGoal: Double? = null, + source: String, ): SpendCategorizationState.Empty { return SpendCategorizationState.Empty( header = @@ -74,8 +79,7 @@ constructor(@ApplicationContext private val context: Context) { url = MOVING_ARROW_RIGHT, placeholder = IMAGE_TRANSPARENT_PLACEHOLDER_SMALL, ), - spendGoal = spendGoal, - progress = getSpendGoalProgress(spendGoal, 0.0), + spendGoalState = getSpendGoalState(isSpendGoalEnabled, spendGoal), ), categoryHeaderTitlePrefix = context.resources.getString(R.string.top_spend_categories), title = context.resources.getString(R.string.no_transactions_to_categorise), @@ -90,7 +94,7 @@ constructor(@ApplicationContext private val context: Context) { selectedYear = year, selectedMonth = month, isTotalSyncCompleted = isTotalSyncCompleted, - selfTransferCategory = getSelfTransferCategoryData(categorySummary, mmConfig), + selfTransferCategory = getSelfTransferCategoryData(categorySummary, mmConfig, source), ) } @@ -123,11 +127,13 @@ constructor(@ApplicationContext private val context: Context) { } fun getSpendAnalysisLoadedState( + isSpendGoalEnabled: Boolean, categorySummary: List, mmConfig: MMConfigResponse?, month: Int, year: Int, spendGoal: Double? = null, + source: String, ): SpendCategorizationState.Loaded { return SpendCategorizationState.Loaded( header = @@ -145,9 +151,12 @@ constructor(@ApplicationContext private val context: Context) { ), title = context.resources.getString(R.string.total_spends), subtitle = getTotalSpendsAmount(categorySummary).formatToInrWithDecimals(), - spendGoal = spendGoal, - progress = - getSpendGoalProgress(spendGoal, getTotalSpendsAmount(categorySummary)), + spendGoalState = + getSpendGoalState( + isSpendGoalEnabled, + spendGoal, + getTotalSpendsAmount(categorySummary), + ), actionIcon = IllustrationSource.Remote( MOVING_ARROW_RIGHT, @@ -157,9 +166,9 @@ constructor(@ApplicationContext private val context: Context) { selectedYear = year, selectedMonth = month, categoryHeaderTitlePrefix = context.resources.getString(R.string.top_spend_categories), - categories = getTopCategoriesData(categorySummary, mmConfig, 3), + categories = getTopCategoriesData(categorySummary, mmConfig, 3, source), viewMoreCtaText = context.resources.getString(R.string.view_more), - selfTransferCategory = getSelfTransferCategoryData(categorySummary, mmConfig), + selfTransferCategory = getSelfTransferCategoryData(categorySummary, mmConfig, source), ) } @@ -177,6 +186,7 @@ constructor(@ApplicationContext private val context: Context) { categories: List, mmConfig: MMConfigResponse?, topN: Int, + source: String, ): List { // Filter categories excluding "SELF_TRANSFER" val filteredCategories = getFilteredCategories(categories) @@ -197,7 +207,7 @@ constructor(@ApplicationContext private val context: Context) { ?: return@mapIndexed null SpendCategoryItemData.Loaded( categoryId = category.categoryId.orEmpty(), - name = category.categoryName.orEmpty(), + name = getCategoryName(category, source), iconUrl = IllustrationSource.Remote( url = category.categoryIcon.orEmpty(), @@ -264,6 +274,7 @@ constructor(@ApplicationContext private val context: Context) { categories: List, mmConfig: MMConfigResponse?, topN: Int, + source: String, ): List { // Filter categories excluding "SELF_TRANSFER" val filteredCategories = getFilteredCategories(categories) @@ -288,7 +299,7 @@ constructor(@ApplicationContext private val context: Context) { SpendCategoryItemData.Loaded( categoryId = category.categoryId.orEmpty(), - name = category.categoryName.orEmpty(), + name = getCategoryName(category, source), iconUrl = IllustrationSource.Remote( url = category.categoryIcon.orEmpty(), @@ -310,25 +321,44 @@ constructor(@ApplicationContext private val context: Context) { .filterNotNull() } + private fun getCategoryName(category: CategoryItemData, source: String): String { + return when (source) { + MMJourneySource.NAVI_UPI.name -> { + if (category.categoryId == UNCATEGORIZED) { + context.getString(R.string.others) + } else category.categoryName.orEmpty() + } + else -> category.categoryName.orEmpty() + } + } + private fun getSelfTransferCategoryData( categories: List, mmConfig: MMConfigResponse?, - ): SelfTransferCategoryData { - val selfTransferCategory = categories.find { it.finalCategory == SELF_TRANSFER } - val category = mmConfig?.categories?.firstOrNull { it.categoryId == SELF_TRANSFER } + source: String, + ): SelfTransferCategoryData? { + return when (source) { + MMJourneySource.ACCOUNT_AGGREGATOR.name -> { + val selfTransferCategory = categories.find { it.finalCategory == SELF_TRANSFER } + val category = mmConfig?.categories?.firstOrNull { it.categoryId == SELF_TRANSFER } - return SelfTransferCategoryData( - categoryId = SELF_TRANSFER, - categoryName = - category?.categoryName ?: context.resources.getString(R.string.self_transfer), - subtitle = context.resources.getString(R.string.not_included_in_spends), - amount = selfTransferCategory?.totalAmount.orZero().formatToInrWithDecimals(), - actionIcon = IllustrationSource.Resource(HOME_PAGE_UPI_SECTION_RIGHT_ARROW_ICON), - iconUrl = - IllustrationSource.Remote( - url = category?.categoryIcon.orEmpty(), - placeholder = IMAGE_TRANSPARENT_PLACEHOLDER_MEDIUM, - ), - ) + SelfTransferCategoryData( + categoryId = SELF_TRANSFER, + categoryName = + category?.categoryName + ?: context.resources.getString(R.string.self_transfer), + subtitle = context.resources.getString(R.string.not_included_in_spends), + amount = selfTransferCategory?.totalAmount.orZero().formatToInrWithDecimals(), + actionIcon = + IllustrationSource.Resource(HOME_PAGE_UPI_SECTION_RIGHT_ARROW_ICON), + iconUrl = + IllustrationSource.Remote( + url = category?.categoryIcon.orEmpty(), + placeholder = IMAGE_TRANSPARENT_PLACEHOLDER_MEDIUM, + ), + ) + } + else -> null + } } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/dashboard/provider/DashboardDataProviderImpl.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/dashboard/provider/DashboardDataProviderImpl.kt index 398103a3ad..72f87f442f 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/dashboard/provider/DashboardDataProviderImpl.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/dashboard/provider/DashboardDataProviderImpl.kt @@ -14,6 +14,7 @@ import com.navi.base.utils.EMPTY import com.navi.base.utils.SPACE import com.navi.base.utils.isNotNullAndNotEmpty import com.navi.common.constants.HELP_CTA_TEXT +import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper import com.navi.moneymanager.R import com.navi.moneymanager.common.analytics.DataProviderEventTrackerImpl import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.DashboardBankSectionProviderHelper @@ -35,6 +36,7 @@ import com.navi.moneymanager.common.illustration.repository.ImageRepository.Comp import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.BLACK_ALERT_CIRCLE import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.COMMON_EMPTY_PLACEHOLDER import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.DASHBOARD_BOTTOMSHEET_LOADING +import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.FEEDBACK_ICON import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.IMAGE_TRANSPARENT_PLACEHOLDER_MEDIUM import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.IMAGE_TRANSPARENT_PLACEHOLDER_SMALL import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.INFO @@ -48,10 +50,12 @@ import com.navi.moneymanager.common.model.SpendCategorizationState import com.navi.moneymanager.common.model.ZeroTransactionData import com.navi.moneymanager.common.model.database.AccountOverview import com.navi.moneymanager.common.model.database.TransactionSummaryData +import com.navi.moneymanager.common.model.sectionHeader.MMJourneySource import com.navi.moneymanager.common.network.di.RoomDataStoreInfoProvider import com.navi.moneymanager.common.network.model.MMConfigResponse import com.navi.moneymanager.common.utils.Constants.IS_FIRST_MONTH_SYNC_COMPLETED import com.navi.moneymanager.common.utils.Constants.IS_TOTAL_SYNC_COMPLETED +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE import com.navi.moneymanager.common.utils.Constants.RECENT_TRANSACTION_COUNT import com.navi.moneymanager.common.utils.Constants.USER_NAME import com.navi.moneymanager.common.utils.calculateTimestamps @@ -63,6 +67,7 @@ import com.navi.moneymanager.postonboard.dashboard.model.BankAccountsData import com.navi.moneymanager.postonboard.dashboard.model.BankAccountsLoadingState import com.navi.moneymanager.postonboard.dashboard.model.BankAccountsState import com.navi.moneymanager.postonboard.dashboard.model.BankTransactionsData +import com.navi.moneymanager.postonboard.dashboard.model.FeedbackSectionData import com.navi.moneymanager.postonboard.dashboard.model.FinarkeinErrorBottomSheetData import com.navi.moneymanager.postonboard.dashboard.model.NavBarData import com.navi.moneymanager.postonboard.dashboard.model.RecentTransactionsLoadingItemData @@ -106,6 +111,15 @@ constructor( @ApplicationContext private val context: Context, ) : DashboardDataProvider { + private val dataProviderEventTracker by lazy { + DataProviderEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + + private val isGoalsEnabled = + FirebaseRemoteConfigHelper.getBoolean(FirebaseRemoteConfigHelper.MM_SPEND_GOALS_ENABLED) + @OptIn(FlowPreview::class) private suspend fun getTransactionCountFlow(): Flow { return executeQueryFlow( @@ -200,7 +214,7 @@ constructor( Triple(isCurrentMonthSynced, isTotalDataSynced, accounts) }, flatMapLatestTransform = { (isCurrentMonthSynced, isTotalDataSynced, accounts) -> - DataProviderEventTrackerImpl.dashboardDpBankSectionEmission( + dataProviderEventTracker.dashboardDpBankSectionEmission( isCurrentMonthSynced, isTotalDataSynced, accounts.size, @@ -218,7 +232,7 @@ constructor( return if (accounts.any { account -> account.currentBalance != null }) { val aggregate = bankSectionProviderHelper.createAggregateAccount(accounts) val accountsList = bankSectionProviderHelper.createAccountList(accounts) - DataProviderEventTrackerImpl.dashboardDpBankSectionLoaded( + dataProviderEventTracker.dashboardDpBankSectionLoaded( accountsListSize = accountsList.size, isTotalSyncCompleted, ) @@ -238,7 +252,7 @@ constructor( ) ) } else { - DataProviderEventTrackerImpl.dashboardDpBankSectionLoading() + dataProviderEventTracker.dashboardDpBankSectionLoading() BankAccountsState.Loading( data = BankAccountsLoadingState( @@ -300,7 +314,7 @@ constructor( flatMapLatestTransform = { (syncStatus, params, spendGoal) -> val (isFirstMonthSyncCompleted, isTotalSyncCompleted) = syncStatus val dateComponents = getDayMonthAndYearFromTimestamp(System.currentTimeMillis()) - DataProviderEventTrackerImpl.dashboardDpSpendAnalysisSectionEmission( + dataProviderEventTracker.dashboardDpSpendAnalysisSectionEmission( params.month.toString(), params.year.toString(), ) @@ -324,6 +338,7 @@ constructor( val state = getSpendCategorizationSectionState( + isSpendGoalEnabled = isGoalsEnabled, month = params.month ?: dateComponents.second, year = params.year ?: dateComponents.third, isCurrentMonthSyncCompleted = isFirstMonthSyncCompleted, @@ -337,6 +352,7 @@ constructor( } private suspend fun getSpendCategorizationSectionState( + isSpendGoalEnabled: Boolean, isCurrentMonthSyncCompleted: Boolean, month: Int, year: Int, @@ -345,7 +361,7 @@ constructor( spendGoal: Double? = null, ): SpendCategorizationState { return if (!isCurrentMonthSyncCompleted) { - DataProviderEventTrackerImpl.dashboardDpSpendAnalysisSectionLoading( + dataProviderEventTracker.dashboardDpSpendAnalysisSectionLoading( month.toString(), year.toString(), isTotalSyncCompleted, @@ -353,7 +369,7 @@ constructor( spendCategorizationHelper.getSpendAnalysisLoadingState(month = month, year = year) } else { if (spendCategorizationHelper.getFilteredCategories(categorySummaryList).isNotEmpty()) { - DataProviderEventTrackerImpl.dashboardDpSpendAnalysisSectionLoaded( + dataProviderEventTracker.dashboardDpSpendAnalysisSectionLoaded( month.toString(), year.toString(), isTotalSyncCompleted, @@ -364,9 +380,11 @@ constructor( month = month, year = year, spendGoal = spendGoal, + isSpendGoalEnabled = isSpendGoalEnabled, + source = MMJourneySource.ACCOUNT_AGGREGATOR.name, ) } else { - DataProviderEventTrackerImpl.dashboardDpSpendAnalysisSectionEmpty( + dataProviderEventTracker.dashboardDpSpendAnalysisSectionEmpty( month.toString(), year.toString(), isTotalSyncCompleted, @@ -378,6 +396,8 @@ constructor( year = year, isTotalSyncCompleted = isTotalSyncCompleted, spendGoal = spendGoal, + isSpendGoalEnabled = isSpendGoalEnabled, + source = MMJourneySource.ACCOUNT_AGGREGATOR.name, ) } } @@ -396,7 +416,7 @@ constructor( }, flatMapLatestTransform = { (isFirstMonthSyncCompleted, isTotalSyncCompleted, accountList) -> - DataProviderEventTrackerImpl.dashboardDpRecentTransactionSectionEmission( + dataProviderEventTracker.dashboardDpRecentTransactionSectionEmission( isFirstMonthSyncCompleted, isTotalSyncCompleted, ) @@ -453,7 +473,7 @@ constructor( return when { isTotalSyncCompleted -> { - DataProviderEventTrackerImpl.dashboardDpRecentTransactionSectionLoaded( + dataProviderEventTracker.dashboardDpRecentTransactionSectionLoaded( isFirstMonthSyncCompleted = isCurrentMonthSyncCompleted, isTotalSyncCompleted = true, transactionsListSize = bankTransactionDataList.size, @@ -462,7 +482,7 @@ constructor( } bankTransactionDataList.flatMap { it.transactions }.isEmpty() -> { - DataProviderEventTrackerImpl.dashboardDpRecentTransactionSectionLoading( + dataProviderEventTracker.dashboardDpRecentTransactionSectionLoading( isFirstMonthSyncCompleted = isCurrentMonthSyncCompleted, isTotalSyncCompleted = isTotalSyncCompleted, transactionsListSize = bankTransactionDataList.size, @@ -471,7 +491,7 @@ constructor( } else -> { - DataProviderEventTrackerImpl.dashboardDpRecentTransactionSectionLoaded( + dataProviderEventTracker.dashboardDpRecentTransactionSectionLoaded( isFirstMonthSyncCompleted = isCurrentMonthSyncCompleted, isTotalSyncCompleted = isTotalSyncCompleted, transactionsListSize = bankTransactionDataList.size, @@ -652,4 +672,18 @@ constructor( spendCategorizationHelper.getSpendAnalysisLoadingState(month, year) override fun getRecentTransactionsLoadingState() = createLoadingTransactions() + + override suspend fun getFeedbackSectionData(): FeedbackSectionData { + return FeedbackSectionData( + title = context.resources.getString(R.string.feedback_title), + subtitle = context.resources.getString(R.string.feedback_subtitle), + buttonLabel = context.resources.getString(R.string.feedback_button_label), + icon = + IllustrationSource.Remote( + FEEDBACK_ICON, + placeholder = IMAGE_TRANSPARENT_PLACEHOLDER_MEDIUM, + ), + bottomSheetTitle = context.resources.getString(R.string.feedback_bottom_sheet_title), + ) + } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/dashboard/provider/UpiSpendDashboardDataProviderImpl.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/dashboard/provider/UpiSpendDashboardDataProviderImpl.kt new file mode 100644 index 0000000000..83a5342bdb --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/dashboard/provider/UpiSpendDashboardDataProviderImpl.kt @@ -0,0 +1,217 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.dataprovider.data.dashboard.provider + +import android.content.Context +import com.navi.base.utils.BaseUtils.getDayMonthAndYearFromTimestamp +import com.navi.base.utils.EMPTY +import com.navi.base.utils.SPACE +import com.navi.moneymanager.R +import com.navi.moneymanager.common.analytics.DataProviderEventTrackerImpl +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.SpendCategorizationHelper +import com.navi.moneymanager.common.dataprovider.data.datastore.DataStoreInfoProvider +import com.navi.moneymanager.common.dataprovider.domain.UpiSpendDashboardDataProvider +import com.navi.moneymanager.common.dataprovider.utils.DataProviderConstants.COMMA +import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQueryFlow +import com.navi.moneymanager.common.dataprovider.utils.toCapitalizedWords +import com.navi.moneymanager.common.db.upi.database.UpiSpendDatabase +import com.navi.moneymanager.common.illustration.model.IllustrationSource +import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.FEEDBACK_ICON +import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.IMAGE_TRANSPARENT_PLACEHOLDER_MEDIUM +import com.navi.moneymanager.common.model.CategorySummary +import com.navi.moneymanager.common.model.SelectedMonth +import com.navi.moneymanager.common.model.SpendCategorizationState +import com.navi.moneymanager.common.model.sectionHeader.MMJourneySource +import com.navi.moneymanager.common.network.di.RoomDataStoreInfoProvider +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE +import com.navi.moneymanager.common.utils.Constants.USER_NAME +import com.navi.moneymanager.common.utils.combineWithFlatMapLatest +import com.navi.moneymanager.postonboard.dashboard.model.FeedbackSectionData +import com.navi.moneymanager.postonboard.dashboard.model.NavBarData +import com.navi.moneymanager.postonboard.dashboard.model.UserHeaderState +import dagger.Lazy +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull + +class UpiSpendDashboardDataProviderImpl +@Inject +constructor( + private val database: Lazy, + private val mmConfigResponseHelper: MMConfigResponseHelper, + @RoomDataStoreInfoProvider private val dbDataStoreProvider: DataStoreInfoProvider, + private val spendCategorizationHelper: SpendCategorizationHelper, + @ApplicationContext private val context: Context, +) : UpiSpendDashboardDataProvider { + + private val dataProviderEventTracker by lazy { + DataProviderEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + + @OptIn(FlowPreview::class) + private suspend fun getTransactionCountFlow(): Flow { + return executeQueryFlow( + queryName = database.get().transactionsDao()::getTransactionCount.name, + methodName = ::getTransactionCountFlow.name, + flow = database.get().transactionsDao().getTransactionCount(), + ) + .debounce(200) + } + + override suspend fun getMMConfig() = mmConfigResponseHelper.getMMConfig() + + override suspend fun getTopNavBarData() = NavBarData() + + override suspend fun getUserHeader(customerProfileName: String?): UserHeaderState { + val displayName = + customerProfileName + ?: dbDataStoreProvider.getStringData(USER_NAME, EMPTY).first().takeIf { + it.isNotEmpty() + } + ?: context.resources.getString(R.string.hello) + + val greetingText = + if (displayName == context.resources.getString(R.string.hello)) { + displayName.plus(COMMA) + } else { + createGreeting(displayName) + } + + return UserHeaderState.Loaded( + greetingPrompt = greetingText, + expensePrompt = context.resources.getString(R.string.dashboard_heading_title), + ) + } + + private fun createGreeting(displayName: String) = + context + .getString(R.string.hi) + .plus(SPACE) + .plus(displayName.toCapitalizedWords()) + .plus(COMMA) + + override suspend fun getSpendCategorizationSectionStateFlow( + screenParams: MutableStateFlow + ): Flow { + return combineWithFlatMapLatest( + flow1 = screenParams.filterNotNull(), + flow2 = getTransactionCountFlow(), + combineTransform = { params, _ -> params }, + flatMapLatestTransform = { params -> + val dateComponents = getDayMonthAndYearFromTimestamp(System.currentTimeMillis()) + dataProviderEventTracker.dashboardDpSpendAnalysisSectionEmission( + params.month.toString(), + params.year.toString(), + ) + + val categorySummaryList = + executeQueryFlow( + queryName = + database.get().transactionsDao()::getCategorizedTransactionSummary + .name, + methodName = ::getSpendCategorizationSectionStateFlow.name, + flow = + database + .get() + .transactionsDao() + .getCategorizedTransactionSummary( + month = params.month ?: dateComponents.second, + year = params.year ?: dateComponents.third, + ), + ) + .firstOrNull() ?: emptyList() + + val state = + getSpendCategorizationSectionState( + isSpendGoalEnabled = false, + month = params.month ?: dateComponents.second, + year = params.year ?: dateComponents.third, + isCurrentMonthSyncCompleted = true, + isTotalSyncCompleted = true, + categorySummaryList = categorySummaryList, + ) + emit(state) + }, + ) + } + + private suspend fun getSpendCategorizationSectionState( + isSpendGoalEnabled: Boolean, + isCurrentMonthSyncCompleted: Boolean, + month: Int, + year: Int, + isTotalSyncCompleted: Boolean, + categorySummaryList: List, + spendGoal: Double? = null, + ): SpendCategorizationState { + return if (!isCurrentMonthSyncCompleted) { + dataProviderEventTracker.dashboardDpSpendAnalysisSectionLoading( + month.toString(), + year.toString(), + isTotalSyncCompleted, + ) + spendCategorizationHelper.getSpendAnalysisLoadingState(month = month, year = year) + } else { + if (spendCategorizationHelper.getFilteredCategories(categorySummaryList).isNotEmpty()) { + dataProviderEventTracker.dashboardDpSpendAnalysisSectionLoaded( + month.toString(), + year.toString(), + isTotalSyncCompleted, + ) + spendCategorizationHelper.getSpendAnalysisLoadedState( + isSpendGoalEnabled = isSpendGoalEnabled, + categorySummary = categorySummaryList, + mmConfig = getMMConfig(), + month = month, + year = year, + spendGoal = spendGoal, + source = MMJourneySource.NAVI_UPI.name, + ) + } else { + dataProviderEventTracker.dashboardDpSpendAnalysisSectionEmpty( + month.toString(), + year.toString(), + isTotalSyncCompleted, + ) + spendCategorizationHelper.getSpendAnalysisEmptyState( + isSpendGoalEnabled = isSpendGoalEnabled, + categorySummary = categorySummaryList, + mmConfig = getMMConfig(), + month = month, + year = year, + isTotalSyncCompleted = isTotalSyncCompleted, + spendGoal = spendGoal, + source = MMJourneySource.NAVI_UPI.name, + ) + } + } + } + + override suspend fun getFeedbackSectionData(): FeedbackSectionData { + return FeedbackSectionData( + title = context.resources.getString(R.string.feedback_title), + subtitle = context.resources.getString(R.string.feedback_subtitle), + buttonLabel = context.resources.getString(R.string.feedback_button_label), + icon = + IllustrationSource.Remote( + FEEDBACK_ICON, + placeholder = IMAGE_TRANSPARENT_PLACEHOLDER_MEDIUM, + ), + bottomSheetTitle = context.resources.getString(R.string.feedback_bottom_sheet_title), + ) + } +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/remote/RemoteDataProviderImpl.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/remote/RemoteDataProviderImpl.kt index ed7451ec13..f7328e62a7 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/remote/RemoteDataProviderImpl.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/remote/RemoteDataProviderImpl.kt @@ -23,6 +23,7 @@ import com.navi.moneymanager.common.network.model.SpendGoalData import com.navi.moneymanager.common.network.model.SpendGoalRequest import com.navi.moneymanager.common.network.model.SpendGoalsResponse import com.navi.moneymanager.common.network.model.TransactionResponse +import com.navi.moneymanager.common.network.model.UpiSpendTransactionResponse import com.navi.moneymanager.common.network.service.RetrofitService import com.navi.moneymanager.common.utils.Constants.CPS import com.navi.moneymanager.postonboard.monthlysummary.model.PostCategoryTransactionData @@ -168,4 +169,13 @@ class RemoteDataProviderImpl @Inject constructor(private val retrofitService: Re ), ) } + + override suspend fun fetchUpiSpendTransactions( + fromUpdatedAt: Long + ): RepoResult { + return apiResponseCallback( + response = retrofitService.fetchUpiSpendTransactions(fromUpdatedAt = fromUpdatedAt), + metricInfo = MetricInfo.AppMetric(screen = MMScreen.DASHBOARD.screen, isNae = { false }), + ) + } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/remote/upi/UpiSpendLocalDataSyncManagerImpl.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/remote/upi/UpiSpendLocalDataSyncManagerImpl.kt new file mode 100644 index 0000000000..240c7a1897 --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/remote/upi/UpiSpendLocalDataSyncManagerImpl.kt @@ -0,0 +1,63 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.dataprovider.data.remote.upi + +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper +import com.navi.moneymanager.common.dataprovider.data.datastore.DataStoreInfoProvider +import com.navi.moneymanager.common.dataprovider.domain.upi.UpiSpendLocalDataSyncManager +import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQuery +import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQueryFirst +import com.navi.moneymanager.common.db.upi.database.UpiSpendDatabase +import com.navi.moneymanager.common.helper.UpiSpendTransactionsDataHelper +import com.navi.moneymanager.common.network.di.RoomDataStoreInfoProvider +import com.navi.moneymanager.common.network.model.UpiSpendTransactionData +import com.navi.moneymanager.common.utils.Constants.IS_UPI_SPEND_TOTAL_SYNC_COMPLETED +import dagger.Lazy +import javax.inject.Inject +import kotlinx.coroutines.flow.first + +class UpiSpendLocalDataSyncManagerImpl +@Inject +constructor( + private val database: Lazy, + private val transactionsDataHelper: UpiSpendTransactionsDataHelper, + private val mmConfigResponseHelper: Lazy, + @RoomDataStoreInfoProvider private val dbDataStoreProvider: DataStoreInfoProvider, +) : UpiSpendLocalDataSyncManager { + override suspend fun insertUpiTransactions(transactions: List) { + val mmConfig = mmConfigResponseHelper.get().getMMConfig() + executeQuery( + queryName = database.get().transactionsDao()::insertAllTransactions.name, + methodName = ::insertUpiTransactions.name, + query = { + database + .get() + .transactionsDao() + .insertAllTransactions( + transactionsDataHelper.getTransactionsEntity(transactions, mmConfig) + ) + }, + ) + } + + override suspend fun isTotalSyncCompleted(): Boolean { + return dbDataStoreProvider.getBooleanData(IS_UPI_SPEND_TOTAL_SYNC_COMPLETED).first() + } + + override suspend fun getMostRecentTransactionTimestamp(defaultTimestamp: Long): Long { + return executeQueryFirst( + queryName = database.get().transactionsDao()::getLatestTransactionTimestamp.name, + methodName = ::getMostRecentTransactionTimestamp.name, + flow = database.get().transactionsDao().getLatestTransactionTimestamp(), + ) + } + + override suspend fun updateTotalSyncCompleteFlag() { + dbDataStoreProvider.saveBooleanData(IS_UPI_SPEND_TOTAL_SYNC_COMPLETED, true) + } +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/helper/OtherCategoriesBottomSheetDataHelper.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/helper/OtherCategoriesBottomSheetDataHelper.kt index b7081d051b..27287be0cf 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/helper/OtherCategoriesBottomSheetDataHelper.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/helper/OtherCategoriesBottomSheetDataHelper.kt @@ -28,12 +28,14 @@ constructor( fun getOtherCategoriesBottomSheetData( categories: List, mmConfig: MMConfigResponse?, + source: String, ): OtherCategoriesBottomSheetData { val otherCategoriesCategorizedData = spendCategorizationHelper.getOtherCategoriesCategorizedData( categories = categories, mmConfig = mmConfig, topN = Constants.SPEND_CATEGORISATION_TOP_CATEGORIES_SIZE - 1, + source = source, ) val otherCategoriesAccumulatedData = spendCategorizationHelper.getOtherCategoriesAccumulatedData( diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/helper/SpendAnalysisBarGraphSectionHelper.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/helper/SpendAnalysisBarGraphSectionHelper.kt index d32616fd89..0d1886af98 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/helper/SpendAnalysisBarGraphSectionHelper.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/helper/SpendAnalysisBarGraphSectionHelper.kt @@ -15,6 +15,7 @@ import com.navi.moneymanager.common.model.BarGraphData import com.navi.moneymanager.common.model.BarGraphPageData import com.navi.moneymanager.common.model.SelectedMonth import com.navi.moneymanager.common.model.database.TransactionSummaryData +import com.navi.moneymanager.common.model.sectionHeader.MMJourneySource import com.navi.naviwidgets.utils.NaviWidgetIconUtils.NEW_INFO_ICON import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject @@ -28,6 +29,7 @@ class SpendAnalysisBarGraphSectionHelper @Inject constructor(@ApplicationContext currentMonth: Int, currentYear: Int, selectedBankReferenceIds: Set, + journeySource: String, ): BarGraphData { val perPageElementSize = 6 val numMonths = 12 @@ -38,15 +40,22 @@ class SpendAnalysisBarGraphSectionHelper @Inject constructor(@ApplicationContext else SelectedMonth(it, currentYear) } - val spendTransactions = + val transactionsSummaryList = getSpendTransactionsForLastXMonths(transactions, lastXMonthsInOrder.toSet()) val monthlyAmountForAllBanks = - getMonthlySumOfTxnAmount(spendTransactions, lastXMonthsInOrder) + getMonthlySumOfTxnAmount(transactionsSummaryList, lastXMonthsInOrder) - val spendTransactionsForSelectedBanks = - spendTransactions.filter { it.linkedAccRef in selectedBankReferenceIds } + val spendTransactions = + when (journeySource) { + MMJourneySource.NAVI_UPI.name -> { + transactionsSummaryList + } + else -> { + transactionsSummaryList.filter { it.linkedAccRef in selectedBankReferenceIds } + } + } val monthlyAmountForSelectedBanks = - getMonthlySumOfTxnAmount(spendTransactionsForSelectedBanks, lastXMonthsInOrder) + getMonthlySumOfTxnAmount(spendTransactions, lastXMonthsInOrder) val elementList = lastXMonthsInOrder.mapIndexed { index, elementMonth -> diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/helper/SpendAnalysisCategoriesSectionHelper.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/helper/SpendAnalysisCategoriesSectionHelper.kt index a1c96fa30d..1b79a001d3 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/helper/SpendAnalysisCategoriesSectionHelper.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/helper/SpendAnalysisCategoriesSectionHelper.kt @@ -35,6 +35,7 @@ constructor( mmConfig: MMConfigResponse?, month: Int, year: Int, + source: String, ): SpendCategorizationState { if (categories.isEmpty()) { return getEmptySpendCategorizationState(month, year) @@ -52,6 +53,7 @@ constructor( categories = categories, mmConfig = mmConfig, topN = filteredCategories.size, + source = source, ), ) } @@ -62,6 +64,7 @@ constructor( categories = categories, mmConfig = mmConfig, topN = 4, + source = source, ) // Fetch remaining categories excluding top 4 diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/helper/TotalSpendSectionDataHelper.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/helper/TotalSpendSectionDataHelper.kt index 78e49bda4a..e45a3d4bd0 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/helper/TotalSpendSectionDataHelper.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/helper/TotalSpendSectionDataHelper.kt @@ -24,7 +24,7 @@ import com.navi.moneymanager.common.network.model.CategoryItemData import com.navi.moneymanager.common.utils.Constants.DEBIT import com.navi.moneymanager.common.utils.Constants.SELF_TRANSFER import com.navi.moneymanager.common.utils.Constants.UNCATEGORIZED -import com.navi.moneymanager.common.utils.getSpendGoalProgress +import com.navi.moneymanager.common.utils.getSpendGoalState import com.navi.moneymanager.postonboard.categorydetails.model.CategoryTotalSpendSectionCategoryDataData import com.navi.moneymanager.postonboard.spendanalysis.model.TotalSpendSectionData import com.navi.naviwidgets.utils.NaviWidgetIconUtils.DOWN_ARROW_BLACK_16 @@ -36,6 +36,7 @@ class TotalSpendSectionDataHelper @Inject constructor(@ApplicationContext private val context: Context) { fun getTotalSpendSectionData( + isSpendGoalEnabled: Boolean, month: Int, year: Int, currentMonthTransactions: List, @@ -53,8 +54,7 @@ constructor(@ApplicationContext private val context: Context) { ), title = context.resources.getString(R.string.total_spend), amount = sumOfTransactions.formatToInrWithDecimals(), - spendGoal = spendGoal, - progress = getSpendGoalProgress(spendGoal, sumOfTransactions), + spendGoalState = getSpendGoalState(isSpendGoalEnabled, spendGoal, sumOfTransactions), selectedMonth = monthAbbreviations[month].plus(SPACE).plus(year), actionIcon = IllustrationSource.Resource(DOWN_ARROW_BLACK_16), selectedBankReferenceIds = selectedBankReferenceIds.orEmpty(), @@ -74,11 +74,7 @@ constructor(@ApplicationContext private val context: Context) { val spendTransactions = getSpendTransactions(currentMonthTransactions) val sumOfTransactions = spendTransactions.sumOf { it.txnAmount.orZero() } return TotalSpendSectionData( - iconUrl = - IllustrationSource.Remote( - url = categoryData?.categoryIcon ?: TOTAL_SPEND_RUPEE_SYMBOL_WITHOUT_BG, - placeholder = IMAGE_TRANSPARENT_PLACEHOLDER_SMALL, - ), + iconUrl = getIconUrlForSpendSection(categoryData), title = context.resources.getString(R.string.total_spend), amount = sumOfTransactions.formatToInrWithDecimals(), selectedMonth = monthAbbreviations[month].plus(SPACE).plus(year), @@ -133,14 +129,18 @@ constructor(@ApplicationContext private val context: Context) { val selectedBankTitle = selectedAccountEntity ?.let { - "${it.bankCode.orEmpty()} - ${it.maskedAccNumber?.takeLast(4).orEmpty()}" + "${it.bankCode.orEmpty()} - ${ + it.maskedAccNumber?.takeLast(4).orEmpty() + }" } .orEmpty() selectedBankTitle } + availableBanks.size -> { context.resources.getString(R.string.all_accounts) } + else -> { "${selectedBankReferenceIds.size} accounts" } @@ -175,11 +175,71 @@ constructor(@ApplicationContext private val context: Context) { val selectedBankNames = selectedBanks.map { "${it.bankCode.orEmpty()} - ${ - it.maskedAccNumber?.takeLast(4).orEmpty() - }" + it.maskedAccNumber?.takeLast(4).orEmpty() + }" } // Return the list of selected bank names, or null if the list is empty return selectedBankNames.takeIf { it.isNotEmpty() } } + + fun getCategoryTotalSpendSectionDataForUpiSpend( + month: Int, + year: Int, + currentMonthTransactions: List, + categoryData: CategoryItemData?, + ): TotalSpendSectionData { + val spendTransactions = getSpendTransactions(currentMonthTransactions) + val sumOfTransactions = spendTransactions.sumOf { it.txnAmount.orZero() } + return TotalSpendSectionData( + iconUrl = getIconUrlForSpendSection(categoryData), + title = context.resources.getString(R.string.total_spend), + amount = sumOfTransactions.formatToInrWithDecimals(), + selectedMonth = monthAbbreviations[month].plus(SPACE).plus(year), + actionIcon = IllustrationSource.Resource(DOWN_ARROW_BLACK_16), + spendingTrendSectionData = + SectionHeaderData(title = context.resources.getString(R.string.past_spends)), + selectedBankReferenceIds = emptySet(), + category = + CategoryTotalSpendSectionCategoryDataData( + categoryId = categoryData?.categoryId.orEmpty(), + categoryName = + if (categoryData?.categoryId == UNCATEGORIZED) + context.getString(R.string.others) + else categoryData?.categoryName.orEmpty(), + ), + ) + } + + fun getTotalSpendSectionDataForUpiSpend( + isSpendGoalEnabled: Boolean, + month: Int, + year: Int, + currentMonthTransactions: List, + spendGoal: Double? = null, + ): TotalSpendSectionData { + val spendTransactions = getSpendTransactions(currentMonthTransactions) + val sumOfTransactions = spendTransactions.sumOf { it.txnAmount.orZero() } + return TotalSpendSectionData( + iconUrl = + IllustrationSource.Remote( + url = TOTAL_SPEND_RUPEE_SYMBOL_WITHOUT_BG, + placeholder = IMAGE_TRANSPARENT_PLACEHOLDER_SMALL, + ), + title = context.resources.getString(R.string.total_spend), + amount = sumOfTransactions.formatToInrWithDecimals(), + spendGoalState = getSpendGoalState(isSpendGoalEnabled, spendGoal, sumOfTransactions), + selectedMonth = monthAbbreviations[month].plus(SPACE).plus(year), + actionIcon = IllustrationSource.Resource(DOWN_ARROW_BLACK_16), + selectedBankReferenceIds = emptySet(), + spendingTrendSectionData = + SectionHeaderData(title = context.resources.getString(R.string.past_spends)), + ) + } + + private fun getIconUrlForSpendSection(categoryData: CategoryItemData?) = + IllustrationSource.Remote( + url = categoryData?.categoryIcon ?: TOTAL_SPEND_RUPEE_SYMBOL_WITHOUT_BG, + placeholder = IMAGE_TRANSPARENT_PLACEHOLDER_SMALL, + ) } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/provider/CategoryDetailsDataProviderImpl.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/provider/CategoryDetailsDataProviderImpl.kt index cee52b203b..758eb06191 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/provider/CategoryDetailsDataProviderImpl.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/provider/CategoryDetailsDataProviderImpl.kt @@ -9,6 +9,7 @@ package com.navi.moneymanager.common.dataprovider.data.spendanalysis.provider import android.content.Context import com.navi.base.utils.BaseUtils.getDayMonthAndYearFromTimestamp +import com.navi.common.constants.HELP_CTA_TEXT import com.navi.moneymanager.R import com.navi.moneymanager.common.analytics.DataProviderEventTrackerImpl import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper @@ -39,6 +40,7 @@ import com.navi.moneymanager.common.model.database.TransactionSummaryData import com.navi.moneymanager.common.network.di.RoomDataStoreInfoProvider import com.navi.moneymanager.common.utils.Constants import com.navi.moneymanager.common.utils.Constants.IS_TOTAL_SYNC_COMPLETED +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE import com.navi.moneymanager.common.utils.Constants.SELF_TRANSFER import com.navi.moneymanager.common.utils.calculateTimestamps import com.navi.moneymanager.postonboard.categorydetails.model.CategoryDetailsScreenData @@ -46,6 +48,7 @@ import com.navi.moneymanager.postonboard.categorydetails.model.CategorySelection import com.navi.moneymanager.postonboard.categorydetails.model.CategorySelectionBottomSheetData import com.navi.moneymanager.postonboard.categorydetails.model.SortOption import com.navi.moneymanager.postonboard.categorydetails.model.UncategorizedInfoBottomSheetData +import com.navi.moneymanager.postonboard.dashboard.model.NavBarData import dagger.Lazy import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject @@ -69,6 +72,12 @@ constructor( private var currentMonthTransactions = listOf() + private val dataProviderEventTracker by lazy { + DataProviderEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + override suspend fun getSortedTransactions(sortOption: SortOption): List { return when (sortOption) { SortOption.HIGHEST_FIRST -> { @@ -143,7 +152,7 @@ constructor( } val filteredTransactions = getSortedTransactions(sortOption).filter { it.type == Constants.DEBIT } - DataProviderEventTrackerImpl.categoryDetailsDpData( + dataProviderEventTracker.categoryDetailsDpData( month = month.toString(), year = year.toString(), selectedCategory = selectedCategory, @@ -155,6 +164,7 @@ constructor( isTotalSyncCompleted = isTotalSyncCompleted, ) CategoryDetailsScreenData( + topNavBar = NavBarData(actionLabel = HELP_CTA_TEXT), totalSpendSection = totalSpendSectionHelper.getCategoryTotalSpendSectionData( month = selectedMonth, diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/provider/SpendAnalysisDataProviderImpl.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/provider/SpendAnalysisDataProviderImpl.kt index 711c995557..2c0378f5be 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/provider/SpendAnalysisDataProviderImpl.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/provider/SpendAnalysisDataProviderImpl.kt @@ -9,6 +9,8 @@ package com.navi.moneymanager.common.dataprovider.data.spendanalysis.provider import android.content.Context import com.navi.base.utils.BaseUtils.getDayMonthAndYearFromTimestamp +import com.navi.common.constants.HELP_CTA_TEXT +import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper import com.navi.moneymanager.R import com.navi.moneymanager.common.analytics.DataProviderEventTrackerImpl import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper @@ -37,12 +39,15 @@ import com.navi.moneymanager.common.model.DataLoadingBottomSheetData import com.navi.moneymanager.common.model.MonthSelectionBottomSheetData import com.navi.moneymanager.common.model.SelectedMonth import com.navi.moneymanager.common.model.database.TransactionSummaryData +import com.navi.moneymanager.common.model.sectionHeader.MMJourneySource import com.navi.moneymanager.common.network.di.RoomDataStoreInfoProvider import com.navi.moneymanager.common.utils.Constants.DEBIT import com.navi.moneymanager.common.utils.Constants.IS_TOTAL_SYNC_COMPLETED +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE import com.navi.moneymanager.common.utils.Constants.SELF_TRANSFER import com.navi.moneymanager.common.utils.calculateTimestamps import com.navi.moneymanager.common.utils.monthStartTimestamp +import com.navi.moneymanager.postonboard.dashboard.model.NavBarData import com.navi.moneymanager.postonboard.spendanalysis.model.OtherCategoriesBottomSheetData import com.navi.moneymanager.postonboard.spendanalysis.model.SpendAnalysisScreenData import com.navi.moneymanager.postonboard.spendanalysis.model.dailySpendingData.DailySpendingData @@ -70,6 +75,15 @@ constructor( @ApplicationContext private val context: Context, ) : SpendAnalysisLocalDataProvider { + private val dataProviderEventTracker by lazy { + DataProviderEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + + private val isGoalsEnabled = + FirebaseRemoteConfigHelper.getBoolean(FirebaseRemoteConfigHelper.MM_SPEND_GOALS_ENABLED) + override suspend fun getSpendAnalysisScreenData( month: Int?, year: Int?, @@ -137,7 +151,7 @@ constructor( methodName = ::getSpendAnalysisScreenData.name, flow = database.get().spendGoalsDao().fetchGoal(monthStartTimestamp), ) - DataProviderEventTrackerImpl.spendAnalysisDpData( + dataProviderEventTracker.spendAnalysisDpData( month = month.toString(), year = year.toString(), selectedBanksListSize = selectedBankReferenceIds?.size.toString(), @@ -154,8 +168,10 @@ constructor( ) SpendAnalysisScreenData( + topNavBar = NavBarData(actionLabel = HELP_CTA_TEXT), totalSpendSection = totalSpendSectionHelper.getTotalSpendSectionData( + isSpendGoalEnabled = isGoalsEnabled, month = month ?: dateComponents.second, year = year ?: dateComponents.third, currentMonthTransactions = currentMonthTransactions, @@ -169,6 +185,7 @@ constructor( mmConfig = mmConfig, month = month ?: dateComponents.second, year = year ?: dateComponents.third, + source = MMJourneySource.ACCOUNT_AGGREGATOR.name, ), barGraphData = barGraphSectionHelper.getBarGraphSectionData( @@ -269,6 +286,7 @@ constructor( return otherCategoriesBottomSheetDataHelper.getOtherCategoriesBottomSheetData( categories, mmConfig, + source = MMJourneySource.ACCOUNT_AGGREGATOR.name, ) } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/provider/UpiSpendAnalysisDataProviderImpl.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/provider/UpiSpendAnalysisDataProviderImpl.kt new file mode 100644 index 0000000000..e18e6658a9 --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/provider/UpiSpendAnalysisDataProviderImpl.kt @@ -0,0 +1,225 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.dataprovider.data.spendanalysis.provider + +import android.content.Context +import com.navi.base.utils.BaseUtils.getDayMonthAndYearFromTimestamp +import com.navi.moneymanager.R +import com.navi.moneymanager.common.analytics.DataProviderEventTrackerImpl +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper +import com.navi.moneymanager.common.dataprovider.data.spendanalysis.helper.DailySpendingGraphDataHelper +import com.navi.moneymanager.common.dataprovider.data.spendanalysis.helper.OtherCategoriesBottomSheetDataHelper +import com.navi.moneymanager.common.dataprovider.data.spendanalysis.helper.SpendAnalysisBarGraphSectionHelper +import com.navi.moneymanager.common.dataprovider.data.spendanalysis.helper.SpendAnalysisCategoriesSectionHelper +import com.navi.moneymanager.common.dataprovider.data.spendanalysis.helper.TotalSpendSectionDataHelper +import com.navi.moneymanager.common.dataprovider.data.transaction.helper.TransactionProviderHelper +import com.navi.moneymanager.common.dataprovider.domain.upi.UpiSpendAnalysisLocalDataProvider +import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQueryFirst +import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQueryFlow +import com.navi.moneymanager.common.db.upi.database.UpiSpendDatabase +import com.navi.moneymanager.common.illustration.model.IllustrationSource +import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.COMMON_CROSS_BLACK +import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.IMAGE_TRANSPARENT_PLACEHOLDER_SMALL +import com.navi.moneymanager.common.model.DailySpendBottomSheetData +import com.navi.moneymanager.common.model.DailySpendBottomSheetHeaderData +import com.navi.moneymanager.common.model.SelectedMonth +import com.navi.moneymanager.common.model.database.TransactionSummaryData +import com.navi.moneymanager.common.model.database.toTransactionSummaryData +import com.navi.moneymanager.common.model.sectionHeader.MMJourneySource +import com.navi.moneymanager.common.utils.Constants.DEBIT +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE +import com.navi.moneymanager.common.utils.Constants.SELF_TRANSFER +import com.navi.moneymanager.common.utils.calculateTimestamps +import com.navi.moneymanager.postonboard.spendanalysis.model.OtherCategoriesBottomSheetData +import com.navi.moneymanager.postonboard.spendanalysis.model.SpendAnalysisScreenData +import com.navi.moneymanager.postonboard.spendanalysis.model.dailySpendingData.DailySpendingData +import dagger.Lazy +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import timber.log.Timber + +class UpiSpendAnalysisDataProviderImpl +@Inject +constructor( + private val database: Lazy, + private val mmConfigResponseHelper: MMConfigResponseHelper, + private val totalSpendSectionHelper: TotalSpendSectionDataHelper, + private val categoriesSectionHelper: SpendAnalysisCategoriesSectionHelper, + private val barGraphSectionHelper: SpendAnalysisBarGraphSectionHelper, + private val otherCategoriesBottomSheetDataHelper: OtherCategoriesBottomSheetDataHelper, + private val dailySpendingGraphDataHelper: DailySpendingGraphDataHelper, + private val transactionProviderHelper: TransactionProviderHelper, + @ApplicationContext private val context: Context, +) : UpiSpendAnalysisLocalDataProvider { + + private val dataProviderEventTracker by lazy { + DataProviderEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + + override suspend fun getSpendAnalysisScreenData( + month: Int?, + year: Int?, + ): Flow { + val (startDate, endDate) = calculateTimestamps() + return executeQueryFlow( + queryName = database.get().transactionsDao()::fetchAllTransactions.name, + methodName = ::getSpendAnalysisScreenData.name, + flow = database.get().transactionsDao().fetchAllTransactions(startDate, endDate), + ) + .map { transactions -> + Timber.tag("money_manager") + .d( + "SpendAnalysisDataProvider ${::getSpendAnalysisScreenData.name}: transactions fetched from DB" + ) + val dateComponents = getDayMonthAndYearFromTimestamp(System.currentTimeMillis()) + + val transactionsList = transactions.map { it.toTransactionSummaryData() } + val currentMonthTransactions = + transactionsList.filter { + it.txnMonth == (month ?: dateComponents.second) && + it.txnYear == (year ?: dateComponents.third) + } + val mmConfig = mmConfigResponseHelper.getMMConfig() + val categories = + executeQueryFirst( + queryName = + database.get().transactionsDao()::getCategorizedTransactionSummary.name, + methodName = ::getSpendAnalysisScreenData.name, + flow = + database + .get() + .transactionsDao() + .getCategorizedTransactionSummary( + month = month ?: dateComponents.second, + year = year ?: dateComponents.third, + ), + ) + + dataProviderEventTracker.spendAnalysisDpData( + month = month.toString(), + year = year.toString(), + selectedBanksListSize = 0, + currentMonthsTransactionsListSize = currentMonthTransactions.size, + transactionsListSize = transactions.size, + accountsListSize = 0, + categoriesListSize = categories.size, + isTotalSyncCompleted = true, + ) + + val dailySpendList = + dailySpendingGraphDataHelper.getDailySpendList( + getMonthlySpendsFromTransactions(currentMonthTransactions) + ) + + SpendAnalysisScreenData( + totalSpendSection = + totalSpendSectionHelper.getTotalSpendSectionDataForUpiSpend( + isSpendGoalEnabled = false, + month = month ?: dateComponents.second, + year = year ?: dateComponents.third, + currentMonthTransactions = currentMonthTransactions, + ), + spendCategorizationState = + categoriesSectionHelper.getCategoriesSectionData( + categories = categories, + mmConfig = mmConfig, + month = month ?: dateComponents.second, + year = year ?: dateComponents.third, + source = MMJourneySource.NAVI_UPI.name, + ), + barGraphData = + barGraphSectionHelper.getBarGraphSectionData( + selectedMonth = + SelectedMonth( + month = month ?: dateComponents.second, + year = year ?: dateComponents.third, + ), + transactions = transactionsList, + isTotalSyncCompleted = true, + currentMonth = dateComponents.second, + currentYear = dateComponents.third, + selectedBankReferenceIds = emptySet(), + journeySource = MMJourneySource.NAVI_UPI.name, + ), + dailySpendingData = + DailySpendingData( + dailySpendList = dailySpendList, + sectionTitle = context.resources.getString(R.string.daily_spends), + highlightedDailySpend = + dailySpendList.lastOrNull { dailySpend -> + dailySpend.amount == dailySpendList.maxOfOrNull { it.amount } + }, + ), + ) + } + } + + private fun getMonthlySpendsFromTransactions( + transactions: List + ): List { + return transactions.filter { + it.txnTimestamp != null && it.type == DEBIT && it.finalCategory != SELF_TRANSFER + } + } + + override suspend fun getOtherCategoriesBottomSheetData( + month: Int?, + year: Int?, + ): OtherCategoriesBottomSheetData { + val dateComponents = getDayMonthAndYearFromTimestamp(System.currentTimeMillis()) + val mmConfig = mmConfigResponseHelper.getMMConfig() + val categories = + executeQueryFirst( + queryName = database.get().transactionsDao()::getCategorizedTransactionSummary.name, + methodName = ::getOtherCategoriesBottomSheetData.name, + flow = + database + .get() + .transactionsDao() + .getCategorizedTransactionSummary( + month = month ?: dateComponents.second, + year = year ?: dateComponents.third, + ), + ) + return otherCategoriesBottomSheetDataHelper.getOtherCategoriesBottomSheetData( + categories, + mmConfig, + source = MMJourneySource.NAVI_UPI.name, + ) + } + + override suspend fun getDailySpendBottomSheetData( + dailySpendData: DailySpendingData.DailySpend + ): DailySpendBottomSheetData { + return DailySpendBottomSheetData( + headerData = + DailySpendBottomSheetHeaderData( + title = dailySpendData.formattedAmount, + subTitle = + String.format( + context.resources.getString(R.string.daily_spends_bottomsheet_subtitle), + dailySpendData.transactions.size, + dailySpendData.formattedDateWithWeek, + ), + iconUrl = + IllustrationSource.Remote( + url = COMMON_CROSS_BLACK, + placeholder = IMAGE_TRANSPARENT_PLACEHOLDER_SMALL, + ), + ), + transactions = + transactionProviderHelper.createTransactionList(dailySpendData.transactions), + ctaText = context.resources.getString(R.string.mm_transaction_dashboard_view_all), + isFooterVisible = false, + ) + } +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/provider/UpiSpendCategoryDetailsLocalDataProviderImpl.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/provider/UpiSpendCategoryDetailsLocalDataProviderImpl.kt new file mode 100644 index 0000000000..df8554702f --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendanalysis/provider/UpiSpendCategoryDetailsLocalDataProviderImpl.kt @@ -0,0 +1,195 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.dataprovider.data.spendanalysis.provider + +import android.content.Context +import com.navi.base.utils.BaseUtils.getDayMonthAndYearFromTimestamp +import com.navi.moneymanager.R +import com.navi.moneymanager.common.analytics.DataProviderEventTrackerImpl +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper +import com.navi.moneymanager.common.dataprovider.data.spendanalysis.helper.SpendAnalysisBarGraphSectionHelper +import com.navi.moneymanager.common.dataprovider.data.spendanalysis.helper.TotalSpendSectionDataHelper +import com.navi.moneymanager.common.dataprovider.data.transaction.helper.TransactionProviderHelper +import com.navi.moneymanager.common.dataprovider.domain.UpiSpendCategoryDetailsLocalDataProvider +import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQueryFlow +import com.navi.moneymanager.common.db.upi.database.UpiSpendDatabase +import com.navi.moneymanager.common.illustration.model.IllustrationSource +import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.COMMON_EMPTY_PLACEHOLDER +import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.IMAGE_TRANSPARENT_PLACEHOLDER_MEDIUM +import com.navi.moneymanager.common.model.SelectedMonth +import com.navi.moneymanager.common.model.Transaction +import com.navi.moneymanager.common.model.ZeroTransactionData +import com.navi.moneymanager.common.model.database.TransactionSummaryData +import com.navi.moneymanager.common.model.database.toTransactionSummaryData +import com.navi.moneymanager.common.utils.Constants +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE +import com.navi.moneymanager.common.utils.Constants.SELF_TRANSFER +import com.navi.moneymanager.common.utils.Constants.UNCATEGORIZED +import com.navi.moneymanager.common.utils.calculateTimestamps +import com.navi.moneymanager.postonboard.categorydetails.model.CategoryDetailsScreenData +import com.navi.moneymanager.postonboard.categorydetails.model.CategorySelectionBottomSheetCategoryData +import com.navi.moneymanager.postonboard.categorydetails.model.CategorySelectionBottomSheetData +import com.navi.moneymanager.postonboard.categorydetails.model.SortOption +import com.navi.moneymanager.postonboard.dashboard.model.NavBarData +import dagger.Lazy +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import timber.log.Timber + +class UpiSpendCategoryDetailsLocalDataProviderImpl +@Inject +constructor( + private val database: Lazy, + private val mmConfigResponseHelper: MMConfigResponseHelper, + private val totalSpendSectionHelper: TotalSpendSectionDataHelper, + private val barGraphSectionHelper: SpendAnalysisBarGraphSectionHelper, + private val transactionProviderHelper: TransactionProviderHelper, + @ApplicationContext private val context: Context, +) : UpiSpendCategoryDetailsLocalDataProvider { + + private var currentMonthTransactions = listOf() + + private val dataProviderEventTracker by lazy { + DataProviderEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + + override suspend fun getSortedTransactions(sortOption: SortOption): List { + return when (sortOption) { + SortOption.HIGHEST_FIRST -> { + transactionProviderHelper.createTransactionList( + currentMonthTransactions.sortedByDescending { it.txnAmount ?: 0.0 } + ) + } + SortOption.LOWEST_FIRST -> { + transactionProviderHelper.createTransactionList( + currentMonthTransactions.sortedBy { it.txnAmount ?: 0.0 } + ) + } + SortOption.RECENT_FIRST -> { + transactionProviderHelper.createTransactionList( + currentMonthTransactions.sortedByDescending { it.txnTimestamp ?: 0L } + ) + } + } + } + + override suspend fun getCategoryDetailsScreenData( + month: Int?, + year: Int?, + sortOption: SortOption, + selectedCategory: String, + ): Flow { + val (startDate, endDate) = calculateTimestamps() + return executeQueryFlow( + queryName = database.get().transactionsDao()::fetchCategorisedTransactions.name, + methodName = ::getCategoryDetailsScreenData.name, + flow = + database + .get() + .transactionsDao() + .fetchCategorisedTransactions(selectedCategory, startDate, endDate), + ) + .map { transactions -> + Timber.tag("money_manager") + .d( + "CategoryDetailsDataProvider ${::getCategoryDetailsScreenData.name}: transactions fetched from db with category: $selectedCategory" + ) + val dateComponents = getDayMonthAndYearFromTimestamp(System.currentTimeMillis()) + val selectedMonth = month ?: dateComponents.second + val selectedYear = year ?: dateComponents.third + val transactionsList = transactions.map { it.toTransactionSummaryData() } + currentMonthTransactions = + transactionsList.filter { + it.txnMonth == selectedMonth && it.txnYear == selectedYear + } + + val categoryData = + mmConfigResponseHelper.getMMConfig()?.categories?.find { + it.categoryId == selectedCategory + } + val filteredTransactions = + getSortedTransactions(sortOption).filter { it.type == Constants.DEBIT } + dataProviderEventTracker.categoryDetailsDpData( + month = month.toString(), + year = year.toString(), + selectedCategory = selectedCategory, + selectedBanksListSize = 0, + transactionsListSize = transactions.size, + currentMonthsTransactionsListSize = currentMonthTransactions.size, + filteredTransactionsListSize = filteredTransactions.size, + accountsListSize = 0, + isTotalSyncCompleted = true, + ) + CategoryDetailsScreenData( + totalSpendSection = + totalSpendSectionHelper.getCategoryTotalSpendSectionDataForUpiSpend( + month = selectedMonth, + year = selectedYear, + currentMonthTransactions = currentMonthTransactions, + categoryData = categoryData, + ), + barGraphData = + barGraphSectionHelper.getBarGraphSectionData( + selectedMonth = SelectedMonth(selectedMonth, selectedYear), + transactions = transactionsList, + isTotalSyncCompleted = true, + currentMonth = dateComponents.second, + currentYear = dateComponents.third, + selectedBankReferenceIds = setOf(), + journeySource = mmConfigResponseHelper.journeySource, + ), + transactions = filteredTransactions, + sortOption = sortOption, + zeroTransactionData = + ZeroTransactionData( + title = context.getString(R.string.mm_transaction_zero_title), + illustrationSource = + IllustrationSource.Remote( + url = COMMON_EMPTY_PLACEHOLDER, + placeholder = IMAGE_TRANSPARENT_PLACEHOLDER_MEDIUM, + ), + ), + ) + } + } + + override suspend fun getCategorySelectionBottomSheetData( + selectedCategory: String + ): CategorySelectionBottomSheetData { + val categoryListFromConfig = + mmConfigResponseHelper.getMMConfig()?.categories?.filter { + it.categoryId != SELF_TRANSFER + } + val categoryList = + categoryListFromConfig + ?.map { + CategorySelectionBottomSheetCategoryData( + categoryId = it.categoryId.orEmpty(), + categoryName = + if (it.categoryId == UNCATEGORIZED) context.getString(R.string.others) + else it.categoryName.orEmpty(), + isSelected = selectedCategory == it.categoryId, + iconUrl = it.categoryIcon.orEmpty(), + ) + } + .orEmpty() + + return CategorySelectionBottomSheetData( + selectedCategory = selectedCategory, + headerTitle = context.getString(R.string.select_category), + ctaText = context.getString(R.string.apply_pascal_case), + categoryList = categoryList, + ) + } + + override suspend fun getTopNavBarData() = NavBarData() +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendgoal/helper/SpendGoalBarGraphSectionHelper.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendgoal/helper/SpendGoalBarGraphSectionHelper.kt index 8a39cbc199..6b9510fe9e 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendgoal/helper/SpendGoalBarGraphSectionHelper.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/spendgoal/helper/SpendGoalBarGraphSectionHelper.kt @@ -28,6 +28,7 @@ class SpendGoalBarGraphSectionHelper @Inject constructor(@ApplicationContext con currentMonth: Int, currentYear: Int, selectedBankReferenceIds: Set, + journeySource: String, ): BarGraphData { val perPageElementSize = 6 val numMonths = 6 diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/transaction/helper/TransactionProviderHelper.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/transaction/helper/TransactionProviderHelper.kt index 8c285a56d6..0aa4b0d718 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/transaction/helper/TransactionProviderHelper.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/transaction/helper/TransactionProviderHelper.kt @@ -20,6 +20,7 @@ import com.navi.moneymanager.common.dataprovider.utils.getTransactionDate import com.navi.moneymanager.common.dataprovider.utils.getTransactionMonth import com.navi.moneymanager.common.dataprovider.utils.getTransactionYear import com.navi.moneymanager.common.illustration.model.IllustrationSource +import com.navi.moneymanager.common.illustration.repository.ImageRepository import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.ALL_BANK_ICON_SMALL import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.IMAGE_TRANSPARENT_PLACEHOLDER_SMALL import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.PLUS @@ -27,11 +28,13 @@ import com.navi.moneymanager.common.model.MonthAmountAggregate import com.navi.moneymanager.common.model.MonthlyBalance import com.navi.moneymanager.common.model.Transaction import com.navi.moneymanager.common.model.database.TransactionSummaryData +import com.navi.moneymanager.common.model.sectionHeader.MMJourneySource import com.navi.moneymanager.common.network.model.MMConfigResponse import com.navi.moneymanager.common.utils.Constants.CREDIT import com.navi.moneymanager.common.utils.Constants.DEBIT import com.navi.moneymanager.common.utils.Constants.PLUS_SPACE import com.navi.moneymanager.common.utils.Constants.UNCATEGORIZED +import com.navi.moneymanager.common.utils.getDateFormatPattern import com.navi.moneymanager.postonboard.dashboard.model.BankTransactionsData import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject @@ -52,7 +55,7 @@ constructor( suspend fun createTransaction(transaction: TransactionSummaryData): Transaction { val mmConfig = mmConfigResponseHelper.getMMConfig() - + val dateFormatPattern = getDateFormatPattern(mmConfigResponseHelper.journeySource) return Transaction( id = transaction.txnId, initials = getInitials(transaction.counterPartyName.orEmpty()), @@ -60,19 +63,21 @@ constructor( title = transaction.counterPartyName ?: context.getString(R.string.unknown), categoryName = getCategoryName(transaction.finalCategory, mmConfig), categoryIcon = getCategoryIcon(transaction.finalCategory), - date = getTransactionDate(transaction.txnTimestamp), + date = getTransactionDate(transaction.txnTimestamp, dateFormatPattern), monthTag = getMonthTag(transaction.txnTimestamp), amount = getTransactionAmount(transaction.txnAmount, transaction.type), transactionTypeLabel = getTransactionTypeLabel(transaction.type), accountIcon = - IllustrationSource.Remote( - url = transaction.bankIconUrl.orEmpty(), - placeholder = ALL_BANK_ICON_SMALL, + getTransactionIcon( + journeySource = mmConfigResponseHelper.journeySource, + bankIconUrl = transaction.bankIconUrl, ), categoryId = transaction.finalCategory, counterPartyName = transaction.counterPartyName.orEmpty(), type = transaction.type.orEmpty(), txnTimestamp = transaction.txnTimestamp.orZero(), + isCategoryClickable = + mmConfigResponseHelper.journeySource != MMJourneySource.NAVI_UPI.name, ) } @@ -108,13 +113,23 @@ constructor( mmConfig?.categories?.firstOrNull { it.categoryId == categoryId }?.categoryName return categoryName?.takeIf { it.isNotEmpty() && categoryId != UNCATEGORIZED } - ?: context.getString(R.string.add_category) + ?: run { + when (mmConfigResponseHelper.journeySource) { + MMJourneySource.NAVI_UPI.name -> context.getString(R.string.others) + else -> context.getString(R.string.add_category) + } + } } private fun getCategoryIcon(categoryId: String?): IllustrationSource? { - return if (categoryId != UNCATEGORIZED) null - else + return if ( + categoryId == UNCATEGORIZED && + mmConfigResponseHelper.journeySource != MMJourneySource.NAVI_UPI.name + ) { IllustrationSource.Remote(url = PLUS, placeholder = IMAGE_TRANSPARENT_PLACEHOLDER_SMALL) + } else { + null + } } private fun getMonthTag(transactionTime: Long?) = @@ -142,4 +157,16 @@ constructor( } } } + + private fun getTransactionIcon( + journeySource: String, + bankIconUrl: String?, + ): IllustrationSource { + val url = + when (journeySource) { + MMJourneySource.NAVI_UPI.name -> ImageRepository.NAVI_LOGO + else -> bankIconUrl.orEmpty() + } + return IllustrationSource.Remote(url = url, placeholder = ALL_BANK_ICON_SMALL) + } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/transaction/provider/TransactionDetailsDataProviderImpl.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/transaction/provider/TransactionDetailsDataProviderImpl.kt index f3fd9419d9..d063159184 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/transaction/provider/TransactionDetailsDataProviderImpl.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/transaction/provider/TransactionDetailsDataProviderImpl.kt @@ -36,6 +36,7 @@ import com.navi.moneymanager.common.model.database.TransactionDetails import com.navi.moneymanager.common.network.model.MMConfigResponse import com.navi.moneymanager.common.utils.Constants.CREDIT import com.navi.moneymanager.common.utils.Constants.DEBIT +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE import com.navi.moneymanager.common.utils.Constants.UNCATEGORIZED import com.navi.moneymanager.postonboard.monthlysummary.model.CategoryBottomSheetTransactionData import com.navi.moneymanager.postonboard.transactiondetails.model.AccountHolderInfo @@ -59,6 +60,13 @@ constructor( private val mmConfigResponseHelper: MMConfigResponseHelper, @ApplicationContext private val context: Context, ) : TransactionDataProvider { + + private val dataProviderEventTracker by lazy { + DataProviderEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + override suspend fun getTransactionDetailScreenData(transactionId: String) = flow { executeQueryFlow( queryName = database.get().transactionsDao()::fetchTransactionDetails.name, @@ -73,7 +81,7 @@ constructor( methodName = "getTransactionDetailScreenData", flow = database.get().accountsDao().fetchAccount(it.linkedAccRef.orEmpty()), ) - DataProviderEventTrackerImpl.transactionDetailsDpData(accountInfo.isNotNull()) + dataProviderEventTracker.transactionDetailsDpData(accountInfo.isNotNull()) emit( TransactionDetailsScreenData( topNavBar = NavBarData(actionLabel = HELP_CTA_TEXT), diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/transaction/provider/TransactionHistoryDataProviderImpl.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/transaction/provider/TransactionHistoryDataProviderImpl.kt index 5f4e4fec31..e6ef3fde00 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/transaction/provider/TransactionHistoryDataProviderImpl.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/transaction/provider/TransactionHistoryDataProviderImpl.kt @@ -14,6 +14,7 @@ import androidx.paging.PagingData import androidx.paging.map import com.navi.moneymanager.R import com.navi.moneymanager.common.analytics.DataProviderEventTrackerImpl +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper import com.navi.moneymanager.common.dataprovider.data.transaction.helper.FilterOptionDataHelper import com.navi.moneymanager.common.dataprovider.data.transaction.helper.TransactionProviderHelper import com.navi.moneymanager.common.dataprovider.domain.TransactionHistoryDataProvider @@ -28,6 +29,7 @@ import com.navi.moneymanager.common.model.FilterAttribute import com.navi.moneymanager.common.model.MonthlyBalance import com.navi.moneymanager.common.model.Transaction import com.navi.moneymanager.common.model.ZeroTransactionData +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE import com.navi.moneymanager.common.utils.Constants.TRANSACTION_HISTORY_PAGE_SIZE import com.navi.moneymanager.common.utils.calculateTimestamps import dagger.Lazy @@ -43,8 +45,16 @@ constructor( private val database: Lazy, private val transactionProviderHelper: TransactionProviderHelper, private val filterOptionDataHelper: FilterOptionDataHelper, + private val mmConfigResponseHelper: MMConfigResponseHelper, @ApplicationContext private val context: Context, ) : TransactionHistoryDataProvider { + + private val dataProviderEventTracker by lazy { + DataProviderEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + override fun getZeroTransactionData() = flow { emit( ZeroTransactionData( @@ -64,7 +74,7 @@ constructor( allFilters: List>>, onMonthlyBalanceCalculated: (List) -> Unit, ): Flow> { - DataProviderEventTrackerImpl.transactionHistoryDpData( + dataProviderEventTracker.transactionHistoryDpData( query.orEmpty(), appliedFilters, allFilters, diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/transaction/provider/upi/UpiSpendTransactionDetailsDataProviderImpl.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/transaction/provider/upi/UpiSpendTransactionDetailsDataProviderImpl.kt new file mode 100644 index 0000000000..dfea85164b --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/transaction/provider/upi/UpiSpendTransactionDetailsDataProviderImpl.kt @@ -0,0 +1,243 @@ +/* + * + * * Copyright © 2024-2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.dataprovider.data.transaction.provider.upi + +import android.content.Context +import com.navi.base.utils.EMPTY +import com.navi.base.utils.formatToInrWithDecimals +import com.navi.base.utils.isNotNullAndNotEmpty +import com.navi.base.utils.orZero +import com.navi.common.extensions.or +import com.navi.moneymanager.R +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper +import com.navi.moneymanager.common.dataprovider.domain.upi.UpiSpendTransactionDataProvider +import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQueryFlow +import com.navi.moneymanager.common.dataprovider.utils.getTransactionDate +import com.navi.moneymanager.common.db.upi.database.UpiSpendDatabase +import com.navi.moneymanager.common.illustration.model.IllustrationSource +import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.ALL_BANK_ICON_SMALL +import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.IMAGE_TRANSPARENT_PLACEHOLDER_SMALL +import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.NAVI_LOGO +import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.OTHERS_CATEGORY_ICON +import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.TRANSACTION_DETAIL_PAYMENT_RECEIVED +import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.TRANSACTION_DETAIL_PAYMENT_SENT +import com.navi.moneymanager.common.model.database.UpiTransactionDetails +import com.navi.moneymanager.common.network.model.MMConfigResponse +import com.navi.moneymanager.common.utils.Constants.CREDIT +import com.navi.moneymanager.common.utils.Constants.DEBIT +import com.navi.moneymanager.common.utils.Constants.UNCATEGORIZED +import com.navi.moneymanager.postonboard.monthlysummary.model.CategoryBottomSheetTransactionData +import com.navi.moneymanager.postonboard.transactiondetails.model.AccountHolderInfo +import com.navi.moneymanager.postonboard.transactiondetails.model.BankAccountDisplayInfo +import com.navi.moneymanager.postonboard.transactiondetails.model.CategoryInfo +import com.navi.moneymanager.postonboard.transactiondetails.model.CounterpartyInfo +import com.navi.moneymanager.postonboard.transactiondetails.model.NavBarData +import com.navi.moneymanager.postonboard.transactiondetails.model.TransactionDetailsScreenData +import com.navi.moneymanager.postonboard.transactiondetails.model.TransactionInfo +import com.navi.moneymanager.postonboard.transactiondetails.model.TransactionMetadataItem +import com.navi.moneymanager.postonboard.transactiondetails.model.TransactionSummary +import dagger.Lazy +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject +import kotlinx.coroutines.flow.flow + +class UpiSpendTransactionDetailsDataProviderImpl +@Inject +constructor( + private val database: Lazy, + private val mmConfigResponseHelper: MMConfigResponseHelper, + @ApplicationContext private val context: Context, +) : UpiSpendTransactionDataProvider { + override suspend fun getTransactionDetailScreenData(transactionId: String) = flow { + executeQueryFlow( + queryName = database.get().transactionsDao()::fetchTransactionDetails.name, + methodName = "getTransactionDetailScreenData", + flow = database.get().transactionsDao().fetchTransactionDetails(transactionId), + ) + .collect { + val mmConfig = mmConfigResponseHelper.getMMConfig() + emit( + TransactionDetailsScreenData( + topNavBar = NavBarData(actionLabel = EMPTY), + transactionInfo = + TransactionInfo( + amount = it.txnAmount.orZero().formatToInrWithDecimals(), + transactionOutcome = getTransactionOutcome(it.type), + transactionDate = getTransactionDate(it.txnTimestamp), + paymentDirectionIcon = getPaymentDirectionIcon(it.type), + ), + categoryInfo = getCategoryInfo(it, mmConfig), + transactionSummary = + TransactionSummary( + counterpartyInfo = + CounterpartyInfo( + paymentNarrative = getCounterPartyPaymentNarrative(it.type), + counterPartyName = + it.counterPartyName.or( + context.getString(R.string.unknown) + ), + ), + accountHolderInfo = + AccountHolderInfo( + paymentNarrative = + getAccountHolderPaymentNarrative(it.type), + bankDetails = + BankAccountDisplayInfo( + bankAccountDisplayText = + context.getString(R.string.navi_upi), + bankIcon = + IllustrationSource.Remote( + url = NAVI_LOGO, + placeholder = ALL_BANK_ICON_SMALL, + ), + ), + ), + transactionMetadata = getTransactionMetadata(it), + ), + ) + ) + } + } + + private fun getTransactionMetadata( + upiTransactionDetails: UpiTransactionDetails + ): List { + val transactionMetadata = mutableListOf() + upiTransactionDetails.upiTxnId + .takeIf { it.isNotBlank() } + ?.let { upiTxnId -> + transactionMetadata.add( + TransactionMetadataItem( + title = context.getString(R.string.upi_transaction_id), + subtitle = upiTxnId, + ) + ) + } + upiTransactionDetails.naviUpiTxnId + .takeIf { it.isNotBlank() } + ?.let { naviUpiTxnId -> + transactionMetadata.add( + TransactionMetadataItem( + title = context.getString(R.string.navi_transaction_id), + subtitle = naviUpiTxnId, + ) + ) + } + return transactionMetadata + } + + private fun getCategoryInfo( + transaction: UpiTransactionDetails, + mmConfig: MMConfigResponse?, + ): CategoryInfo { + return CategoryInfo( + isCategorized = isTransactionCategorized(transaction.finalCategory), + categoryIcon = getCategoryIcon(transaction.finalCategory, mmConfig), + categoryTransactionData = + CategoryBottomSheetTransactionData( + transactionId = transaction.upiTxnId, + categoryId = transaction.finalCategory, + counterPartyName = transaction.counterPartyName.orEmpty(), + categoryName = transaction.finalCategory, + transactionType = transaction.type.orEmpty(), + ), + categoryName = getCategoryName(transaction.finalCategory, mmConfig), + categoryId = transaction.finalCategory, + isClickable = false, + ) + } + + private fun getTransactionOutcome(transactionType: String?): String { + return when (transactionType) { + DEBIT -> { + context.getString(R.string.paid) + } + + CREDIT -> { + context.getString(R.string.received) + } + + else -> { + EMPTY + } + } + } + + private fun getPaymentDirectionIcon(transactionType: String?): IllustrationSource { + return if (transactionType == DEBIT) { + IllustrationSource.Remote( + url = TRANSACTION_DETAIL_PAYMENT_SENT, + placeholder = IMAGE_TRANSPARENT_PLACEHOLDER_SMALL, + ) + } else { + IllustrationSource.Remote( + url = TRANSACTION_DETAIL_PAYMENT_RECEIVED, + placeholder = IMAGE_TRANSPARENT_PLACEHOLDER_SMALL, + ) + } + } + + private fun isTransactionCategorized(transactionCategory: String?): Boolean { + return transactionCategory.isNotNullAndNotEmpty() && transactionCategory != UNCATEGORIZED + } + + private fun getCategoryIcon( + categoryId: String?, + mmConfig: MMConfigResponse?, + ): IllustrationSource { + return mmConfig + ?.categories + ?.firstOrNull { it.categoryId == categoryId } + ?.let { + IllustrationSource.Remote( + url = it.categoryIcon.orEmpty(), + placeholder = IMAGE_TRANSPARENT_PLACEHOLDER_SMALL, + ) + } ?: IllustrationSource.Resource(OTHERS_CATEGORY_ICON) + } + + private fun getCategoryName(categoryId: String?, mmConfig: MMConfigResponse?): String { + val categoryName = + mmConfig?.categories?.firstOrNull { it.categoryId == categoryId }?.categoryName + + return categoryName?.takeIf { it.isNotEmpty() && categoryId != UNCATEGORIZED } + ?: context.getString(R.string.others) + } + + private fun getCounterPartyPaymentNarrative(transactionType: String?): String { + return when (transactionType) { + DEBIT -> { + context.getString(R.string.paid_to) + } + + CREDIT -> { + context.getString(R.string.paid_by) + } + + else -> { + EMPTY + } + } + } + + private fun getAccountHolderPaymentNarrative(transactionType: String?): String { + return when (transactionType) { + DEBIT -> { + context.getString(R.string.paid_via) + } + + CREDIT -> { + context.getString(R.string.received_in) + } + + else -> { + EMPTY + } + } + } +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/upi/TxnHistoryUpiSpendDataProviderImpl.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/upi/TxnHistoryUpiSpendDataProviderImpl.kt new file mode 100644 index 0000000000..1084fc0fc8 --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/data/upi/TxnHistoryUpiSpendDataProviderImpl.kt @@ -0,0 +1,135 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.dataprovider.data.upi + +import android.content.Context +import com.navi.base.utils.BaseUtils.getDayMonthAndYearFromTimestamp +import com.navi.base.utils.EMPTY +import com.navi.base.utils.formatToDecimalPlaces +import com.navi.base.utils.formatToInrWithDecimals +import com.navi.base.utils.orZero +import com.navi.common.utils.PERCENT +import com.navi.moneymanager.R +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper +import com.navi.moneymanager.common.dataprovider.domain.upi.TxnHistoryUpiSpendDataProvider +import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQueryFlow +import com.navi.moneymanager.common.db.upi.database.UpiSpendDatabase +import com.navi.moneymanager.common.illustration.model.IllustrationSource +import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.IMAGE_TRANSPARENT_PLACEHOLDER_MEDIUM +import com.navi.moneymanager.common.model.CategorySummary +import com.navi.moneymanager.common.model.SelectedMonth +import com.navi.moneymanager.common.model.upi.TopSpendCategoryItemData +import com.navi.moneymanager.common.model.upi.UpiTxnHistorySpendAnalysisData +import com.navi.moneymanager.common.network.model.MMConfigResponse +import com.navi.moneymanager.common.ui.theme.color.MMColor.categoriesProgressBarColors +import com.navi.moneymanager.common.utils.Constants.UNCATEGORIZED +import com.navi.moneymanager.common.utils.MonthConstants +import dagger.Lazy +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject +import kotlinx.coroutines.flow.firstOrNull + +class TxnHistoryUpiSpendDataProviderImpl +@Inject +constructor( + private val database: Lazy, + private val mmConfigResponseHelper: MMConfigResponseHelper, + @ApplicationContext private val context: Context, +) : TxnHistoryUpiSpendDataProvider { + override suspend fun getTxnHistoryScreenSummaryData( + selectedMonth: SelectedMonth + ): UpiTxnHistorySpendAnalysisData { + val dateComponents = getDayMonthAndYearFromTimestamp(System.currentTimeMillis()) + val categorySummaryList = + executeQueryFlow( + queryName = + database.get().transactionsDao()::getCategorizedTransactionSummary.name, + methodName = ::getTxnHistoryScreenSummaryData.name, + flow = + database + .get() + .transactionsDao() + .getCategorizedTransactionSummary( + month = selectedMonth.month ?: dateComponents.second, + year = selectedMonth.year ?: dateComponents.third, + ), + ) + .firstOrNull() ?: emptyList() + val mmConfig = mmConfigResponseHelper.getMMConfig() + + getTopCategoryData(categorySummaryList, mmConfig)?.let { + return UpiTxnHistorySpendAnalysisData( + title = + String.format( + context.resources.getString(R.string.your_top_spend_in_month), + MonthConstants.monthNames.getOrNull( + selectedMonth.month ?: dateComponents.second + ) ?: EMPTY, + ) + .uppercase(), + subTitle = null, + spendCategoryItem = it, + ctaText = context.getString(R.string.view_breakdown), + showCrossIcon = false, + selectedMonth = selectedMonth, + ) + } + ?: run { + return UpiTxnHistorySpendAnalysisData( + title = context.getString(R.string.no_spends_yet), + subTitle = context.getString(R.string.no_breakdown_available), + ctaText = context.getString(R.string.view_past_breakdown), + showCrossIcon = false, + selectedMonth = selectedMonth, + ) + } + } + + private fun getTopCategoryData( + categories: List, + mmConfig: MMConfigResponse?, + ): TopSpendCategoryItemData? { + + // Sort categories by totalAmount in descending order + val sortedCategories = categories.sortedByDescending { it.totalAmount } + + // Calculate total amount of all categories + val totalAmount = categories.sumOf { it.totalAmount.orZero() } + + // Calculate percentage for top category + val topCategoryWithPercentage = + sortedCategories.firstOrNull()?.let { item -> + val effectiveTotalAmount = if (totalAmount == 0.0) 1.0 else totalAmount + val progress = item.totalAmount.orZero() / effectiveTotalAmount + val category = + mmConfig?.categories?.firstOrNull { it.categoryId == item.finalCategory } + ?: return null + TopSpendCategoryItemData( + name = + if (category.categoryId == UNCATEGORIZED) context.getString(R.string.others) + else category.categoryName.orEmpty(), + iconUrl = + IllustrationSource.Remote( + url = category.categoryIcon.orEmpty(), + placeholder = IMAGE_TRANSPARENT_PLACEHOLDER_MEDIUM, + ), + progress = progress.toFloat() + 0.03f, // Need to show min 3% for all progress + progressColor = categoriesProgressBarColors[0], + amount = item.totalAmount.orZero().formatToInrWithDecimals(), + progressText = + String.format( + context.resources.getString(R.string.percent_of_all_spend), + if (progress < 0.01) context.getString(R.string.less_than_one_percent) + else (progress * 100).formatToDecimalPlaces(0).plus(PERCENT), + ), + ) + } + + return topCategoryWithPercentage + } +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/di/DataProviderModule.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/di/DataProviderModule.kt index 0a2abafa72..9bcb432b1c 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/di/DataProviderModule.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/di/DataProviderModule.kt @@ -1,6 +1,6 @@ /* * - * * Copyright © 2024 by Navi Technologies Limited + * * Copyright © 2024-2025 by Navi Technologies Limited * * All rights reserved. Strictly confidential * */ @@ -10,12 +10,18 @@ package com.navi.moneymanager.common.dataprovider.di import com.navi.moneymanager.common.dataprovider.data.addcategory.provider.AddCategoryDataProviderImpl import com.navi.moneymanager.common.dataprovider.data.bankaccounts.BankAccountsDataProviderImpl import com.navi.moneymanager.common.dataprovider.data.dashboard.provider.DashboardDataProviderImpl +import com.navi.moneymanager.common.dataprovider.data.dashboard.provider.UpiSpendDashboardDataProviderImpl import com.navi.moneymanager.common.dataprovider.data.remote.LocalDataSyncManagerImpl import com.navi.moneymanager.common.dataprovider.data.remote.RemoteDataProviderImpl +import com.navi.moneymanager.common.dataprovider.data.remote.upi.UpiSpendLocalDataSyncManagerImpl import com.navi.moneymanager.common.dataprovider.data.spendanalysis.provider.CategoryDetailsDataProviderImpl import com.navi.moneymanager.common.dataprovider.data.spendanalysis.provider.SpendAnalysisDataProviderImpl +import com.navi.moneymanager.common.dataprovider.data.spendanalysis.provider.UpiSpendAnalysisDataProviderImpl +import com.navi.moneymanager.common.dataprovider.data.spendanalysis.provider.UpiSpendCategoryDetailsLocalDataProviderImpl import com.navi.moneymanager.common.dataprovider.data.transaction.provider.TransactionDetailsDataProviderImpl import com.navi.moneymanager.common.dataprovider.data.transaction.provider.TransactionHistoryDataProviderImpl +import com.navi.moneymanager.common.dataprovider.data.transaction.provider.upi.UpiSpendTransactionDetailsDataProviderImpl +import com.navi.moneymanager.common.dataprovider.data.upi.TxnHistoryUpiSpendDataProviderImpl import com.navi.moneymanager.common.dataprovider.data.valueprop.provider.LauncherDataProviderImpl import com.navi.moneymanager.common.dataprovider.domain.AddCategoryDataProvider import com.navi.moneymanager.common.dataprovider.domain.BankAccountsDataProvider @@ -27,6 +33,12 @@ import com.navi.moneymanager.common.dataprovider.domain.RemoteDataProvider import com.navi.moneymanager.common.dataprovider.domain.SpendAnalysisLocalDataProvider import com.navi.moneymanager.common.dataprovider.domain.TransactionDataProvider import com.navi.moneymanager.common.dataprovider.domain.TransactionHistoryDataProvider +import com.navi.moneymanager.common.dataprovider.domain.UpiSpendCategoryDetailsLocalDataProvider +import com.navi.moneymanager.common.dataprovider.domain.UpiSpendDashboardDataProvider +import com.navi.moneymanager.common.dataprovider.domain.upi.TxnHistoryUpiSpendDataProvider +import com.navi.moneymanager.common.dataprovider.domain.upi.UpiSpendAnalysisLocalDataProvider +import com.navi.moneymanager.common.dataprovider.domain.upi.UpiSpendLocalDataSyncManager +import com.navi.moneymanager.common.dataprovider.domain.upi.UpiSpendTransactionDataProvider import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -46,6 +58,11 @@ abstract class DataProviderModule { dashboardDataProviderImpl: DashboardDataProviderImpl ): DashboardDataProvider + @Binds + abstract fun bindUpiSpendDashboardLocalDataProvider( + upiSpendDashboardDataProviderImpl: UpiSpendDashboardDataProviderImpl + ): UpiSpendDashboardDataProvider + @Binds abstract fun bindLauncherLocalDataProvider( launcherDataProviderImpl: LauncherDataProviderImpl @@ -85,4 +102,29 @@ abstract class DataProviderModule { abstract fun bindLocalDataSyncManager( localDataSyncManager: LocalDataSyncManagerImpl ): LocalDataSyncManager + + @Binds + abstract fun bindUpiSpendLocalDataSyncManager( + upiSpendLocalDataSyncManager: UpiSpendLocalDataSyncManagerImpl + ): UpiSpendLocalDataSyncManager + + @Binds + abstract fun bindTxnHistoryUpiSpendDataProvider( + txnHistoryUpiSpendDataProvider: TxnHistoryUpiSpendDataProviderImpl + ): TxnHistoryUpiSpendDataProvider + + @Binds + abstract fun bindUpiSpendCategoryDetailsLocalDataProvider( + upiSpendCategoryDetailsDataProviderImpl: UpiSpendCategoryDetailsLocalDataProviderImpl + ): UpiSpendCategoryDetailsLocalDataProvider + + @Binds + abstract fun bindUpiSpendAnalysisLocalDataProvider( + upiSpendAnalysisDataProviderImpl: UpiSpendAnalysisDataProviderImpl + ): UpiSpendAnalysisLocalDataProvider + + @Binds + abstract fun bindUpiSpendTransactionDetailsLocalDataProvider( + upiSpendTransactionDetailsDataProvider: UpiSpendTransactionDetailsDataProviderImpl + ): UpiSpendTransactionDataProvider } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/DashboardDataProvider.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/DashboardDataProvider.kt index 22967b2db9..d1760b188b 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/DashboardDataProvider.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/DashboardDataProvider.kt @@ -15,6 +15,7 @@ import com.navi.moneymanager.common.model.SpendCategorizationState import com.navi.moneymanager.common.network.model.MMConfigResponse import com.navi.moneymanager.postonboard.dashboard.model.BankAccountFooterSection import com.navi.moneymanager.postonboard.dashboard.model.BankAccountsState +import com.navi.moneymanager.postonboard.dashboard.model.FeedbackSectionData import com.navi.moneymanager.postonboard.dashboard.model.FinarkeinErrorBottomSheetData import com.navi.moneymanager.postonboard.dashboard.model.NavBarData import com.navi.moneymanager.postonboard.dashboard.model.RecentTransactionsState @@ -66,4 +67,6 @@ interface DashboardDataProvider : LocalDataProvider { fun getSpendCategorizationLoadingState(month: Int, year: Int): SpendCategorizationState.Loading fun getRecentTransactionsLoadingState(): RecentTransactionsState + + suspend fun getFeedbackSectionData(): FeedbackSectionData } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/RemoteDataProvider.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/RemoteDataProvider.kt index de1a22980a..30a567535c 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/RemoteDataProvider.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/RemoteDataProvider.kt @@ -21,6 +21,7 @@ import com.navi.moneymanager.common.network.model.SpendGoalData import com.navi.moneymanager.common.network.model.SpendGoalRequest import com.navi.moneymanager.common.network.model.SpendGoalsResponse import com.navi.moneymanager.common.network.model.TransactionResponse +import com.navi.moneymanager.common.network.model.UpiSpendTransactionResponse import com.navi.moneymanager.postonboard.monthlysummary.model.PostCategoryTransactionData interface RemoteDataProvider { @@ -67,4 +68,8 @@ interface RemoteDataProvider { ): RepoResult suspend fun deleteGoal(goalId: Int): RepoResult + + suspend fun fetchUpiSpendTransactions( + fromUpdatedAt: Long + ): RepoResult } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/UpiSpendCategoryDetailsLocalDataProvider.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/UpiSpendCategoryDetailsLocalDataProvider.kt new file mode 100644 index 0000000000..32c020ffac --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/UpiSpendCategoryDetailsLocalDataProvider.kt @@ -0,0 +1,33 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.dataprovider.domain + +import com.navi.moneymanager.common.model.Transaction +import com.navi.moneymanager.postonboard.categorydetails.model.CategoryDetailsScreenData +import com.navi.moneymanager.postonboard.categorydetails.model.CategorySelectionBottomSheetData +import com.navi.moneymanager.postonboard.categorydetails.model.SortOption +import com.navi.moneymanager.postonboard.dashboard.model.NavBarData +import kotlinx.coroutines.flow.Flow + +interface UpiSpendCategoryDetailsLocalDataProvider : LocalDataProvider { + + suspend fun getSortedTransactions(sortOption: SortOption): List + + suspend fun getCategoryDetailsScreenData( + month: Int?, + year: Int?, + sortOption: SortOption, + selectedCategory: String, + ): Flow + + suspend fun getTopNavBarData(): NavBarData + + suspend fun getCategorySelectionBottomSheetData( + selectedCategory: String + ): CategorySelectionBottomSheetData +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/UpiSpendDashboardDataProvider.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/UpiSpendDashboardDataProvider.kt new file mode 100644 index 0000000000..e536f9d4c0 --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/UpiSpendDashboardDataProvider.kt @@ -0,0 +1,32 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.dataprovider.domain + +import com.navi.moneymanager.common.model.SelectedMonth +import com.navi.moneymanager.common.model.SpendCategorizationState +import com.navi.moneymanager.common.network.model.MMConfigResponse +import com.navi.moneymanager.postonboard.dashboard.model.FeedbackSectionData +import com.navi.moneymanager.postonboard.dashboard.model.NavBarData +import com.navi.moneymanager.postonboard.dashboard.model.UserHeaderState +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow + +interface UpiSpendDashboardDataProvider : LocalDataProvider { + + suspend fun getMMConfig(): MMConfigResponse? + + suspend fun getTopNavBarData(): NavBarData + + suspend fun getUserHeader(customerProfileName: String?): UserHeaderState + + suspend fun getSpendCategorizationSectionStateFlow( + screenParams: MutableStateFlow + ): Flow + + suspend fun getFeedbackSectionData(): FeedbackSectionData +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/upi/TxnHistoryUpiSpendDataProvider.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/upi/TxnHistoryUpiSpendDataProvider.kt new file mode 100644 index 0000000000..302e7490f6 --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/upi/TxnHistoryUpiSpendDataProvider.kt @@ -0,0 +1,18 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.dataprovider.domain.upi + +import com.navi.moneymanager.common.dataprovider.domain.LocalDataProvider +import com.navi.moneymanager.common.model.SelectedMonth +import com.navi.moneymanager.common.model.upi.UpiTxnHistorySpendAnalysisData + +interface TxnHistoryUpiSpendDataProvider : LocalDataProvider { + suspend fun getTxnHistoryScreenSummaryData( + selectedMonth: SelectedMonth + ): UpiTxnHistorySpendAnalysisData +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/upi/UpiSpendAnalysisLocalDataProvider.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/upi/UpiSpendAnalysisLocalDataProvider.kt new file mode 100644 index 0000000000..f8dffbbbe7 --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/upi/UpiSpendAnalysisLocalDataProvider.kt @@ -0,0 +1,29 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.dataprovider.domain.upi + +import com.navi.moneymanager.common.dataprovider.domain.LocalDataProvider +import com.navi.moneymanager.common.model.DailySpendBottomSheetData +import com.navi.moneymanager.postonboard.spendanalysis.model.OtherCategoriesBottomSheetData +import com.navi.moneymanager.postonboard.spendanalysis.model.SpendAnalysisScreenData +import com.navi.moneymanager.postonboard.spendanalysis.model.dailySpendingData.DailySpendingData +import kotlinx.coroutines.flow.Flow + +interface UpiSpendAnalysisLocalDataProvider : LocalDataProvider { + + suspend fun getSpendAnalysisScreenData(month: Int?, year: Int?): Flow + + suspend fun getOtherCategoriesBottomSheetData( + month: Int?, + year: Int?, + ): OtherCategoriesBottomSheetData + + suspend fun getDailySpendBottomSheetData( + dailySpendData: DailySpendingData.DailySpend + ): DailySpendBottomSheetData +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/upi/UpiSpendLocalDataSyncManager.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/upi/UpiSpendLocalDataSyncManager.kt new file mode 100644 index 0000000000..6952fe2019 --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/upi/UpiSpendLocalDataSyncManager.kt @@ -0,0 +1,21 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.dataprovider.domain.upi + +import com.navi.moneymanager.common.network.model.UpiSpendTransactionData + +interface UpiSpendLocalDataSyncManager { + + suspend fun insertUpiTransactions(transactions: List) + + suspend fun isTotalSyncCompleted(): Boolean + + suspend fun getMostRecentTransactionTimestamp(defaultTimestamp: Long): Long + + suspend fun updateTotalSyncCompleteFlag() +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/upi/UpiSpendTransactionDataProvider.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/upi/UpiSpendTransactionDataProvider.kt new file mode 100644 index 0000000000..66eb6cc134 --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/domain/upi/UpiSpendTransactionDataProvider.kt @@ -0,0 +1,18 @@ +/* + * + * * Copyright © 2024-2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.dataprovider.domain.upi + +import com.navi.moneymanager.common.dataprovider.domain.LocalDataProvider +import com.navi.moneymanager.postonboard.transactiondetails.model.TransactionDetailsScreenData +import kotlinx.coroutines.flow.Flow + +interface UpiSpendTransactionDataProvider : LocalDataProvider { + suspend fun getTransactionDetailScreenData( + transactionId: String + ): Flow +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/utils/DataProviderUtils.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/utils/DataProviderUtils.kt index 5335034ef3..2f51c21f01 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/utils/DataProviderUtils.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/dataprovider/utils/DataProviderUtils.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.graphics.Color import com.navi.base.utils.EMPTY import com.navi.base.utils.SPACE import com.navi.moneymanager.R +import com.navi.moneymanager.common.dataprovider.utils.DataProviderConstants.EE_DD_MMM_YYYY import com.navi.moneymanager.common.ui.theme.color.MMColor import com.navi.moneymanager.common.utils.Constants.CREDIT import com.navi.moneymanager.common.utils.Constants.DEBIT @@ -47,8 +48,9 @@ fun getWeekDayFromTimestamp(timestampMillis: Long): String { return dayOfWeekFormatter.format(date) } -fun getTransactionDate(transactionTime: Long?): String { - return if (transactionTime == null) EMPTY else formatDateFromTimestamp(transactionTime) +fun getTransactionDate(transactionTime: Long?, formatPattern: String = EE_DD_MMM_YYYY): String { + return if (transactionTime == null) EMPTY + else formatDateFromTimestamp(transactionTime, formatPattern) } fun getTransactionMonth(transactionTime: Long?): String { diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/DBSyncExecutor.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/DBSyncExecutor.kt index 9237cd72eb..9922cc3ccc 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/DBSyncExecutor.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/DBSyncExecutor.kt @@ -10,6 +10,7 @@ package com.navi.moneymanager.common.datasync import com.navi.common.utils.log import com.navi.common.utils.safeLaunch import com.navi.moneymanager.common.analytics.DataSyncEventTrackerImpl +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper import com.navi.moneymanager.common.dataprovider.domain.LocalDataSyncManager import com.navi.moneymanager.common.datasync.helper.AllMonthsDataSyncHelper import com.navi.moneymanager.common.datasync.helper.BaseDataSyncHelper.TransactionSyncType @@ -17,6 +18,7 @@ import com.navi.moneymanager.common.datasync.helper.CurrentMonthDataSyncHelper import com.navi.moneymanager.common.datasync.model.DBSyncConfig import com.navi.moneymanager.common.datasync.model.DataSyncState import com.navi.moneymanager.common.datasync.model.DataSyncStatus +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE import javax.inject.Inject import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope @@ -34,6 +36,7 @@ constructor( private val allMonthsDataSyncHelper: AllMonthsDataSyncHelper, private val currentMonthDataSyncHelper: CurrentMonthDataSyncHelper, private val localDataProvider: LocalDataSyncManager, + private val mmConfigResponseHelper: MMConfigResponseHelper, ) { private var currentMonthSyncStatus = MutableStateFlow(DataSyncState.NotStarted) private var allMonthsSyncStatus = MutableStateFlow(DataSyncState.NotStarted) @@ -42,14 +45,20 @@ constructor( private val exceptionHandler = CoroutineExceptionHandler { _, throwable -> throwable.log() } + private val dataSyncEventTracker by lazy { + DataSyncEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + suspend fun execute(dbSyncConfig: DBSyncConfig) { - DataSyncEventTrackerImpl.dbSyncExecutorTriggered() + dataSyncEventTracker.dbSyncExecutorTriggered() syncCurrentMonthIfRequired(dbSyncConfig) syncAllMonthsIfRequired(dbSyncConfig) } fun initDBSyncExecutor(scope: CoroutineScope) { - DataSyncEventTrackerImpl.dbSyncExecutorInit() + dataSyncEventTracker.dbSyncExecutorInit() this.scope = scope dataSyncStatus = combine(currentMonthSyncStatus, allMonthsSyncStatus) { @@ -80,7 +89,7 @@ constructor( } } } else { - DataSyncEventTrackerImpl.syncNotTriggered( + dataSyncEventTracker.syncNotTriggered( type = TransactionSyncType.CURRENT_MONTH.name, pollingStatus = dbSyncConfig.pollingStatusResponse.currMonthTxnsStatus.orEmpty(), ) @@ -103,7 +112,7 @@ constructor( } } } else { - DataSyncEventTrackerImpl.syncNotTriggered( + dataSyncEventTracker.syncNotTriggered( type = "ALL_MONTHS", pollingStatus = dbSyncConfig.pollingStatusResponse.oldMonthTxnsStatus.orEmpty(), ) diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/UpiSpendDBSyncExecutor.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/UpiSpendDBSyncExecutor.kt new file mode 100644 index 0000000000..41446c6391 --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/UpiSpendDBSyncExecutor.kt @@ -0,0 +1,113 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.datasync + +import com.navi.base.utils.orTrue +import com.navi.base.utils.orZero +import com.navi.common.network.models.isSuccessWithData +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper +import com.navi.moneymanager.common.dataprovider.domain.RemoteDataProvider +import com.navi.moneymanager.common.dataprovider.domain.upi.UpiSpendLocalDataSyncManager +import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQuery +import com.navi.moneymanager.common.datasync.model.DataSyncState +import com.navi.moneymanager.common.datasync.model.UpiSpendTransactionsApiCallStatus +import com.navi.moneymanager.common.db.upi.database.UpiSpendDatabase +import com.navi.moneymanager.common.network.model.MMConfigResponse +import com.navi.moneymanager.common.network.model.UpiSpendTransactionData +import dagger.Lazy +import javax.inject.Inject + +class UpiSpendDBSyncExecutor +@Inject +constructor( + private val upiSpendLocalDataSyncManager: UpiSpendLocalDataSyncManager, + private val remoteDataProvider: RemoteDataProvider, + private val upiSpendDatabase: Lazy, + private val mmConfigResponseHelper: MMConfigResponseHelper, +) { + + suspend fun execute(screenName: String): DataSyncState { + val config = fetchAndSaveConfigResponse(screenName) ?: return DataSyncState.Failed + + val cutoffTimestamp = + config.timestampConfig?.twelveMonthsOldTimestamp ?: return DataSyncState.Failed + + val startTimestamp = + upiSpendLocalDataSyncManager.getMostRecentTransactionTimestamp(cutoffTimestamp) + + return if (syncTransactions(startTimestamp)) { + upiSpendLocalDataSyncManager.updateTotalSyncCompleteFlag() + DataSyncState.Completed + } else { + DataSyncState.Failed + } + } + + private suspend fun fetchAndSaveConfigResponse(screenName: String): MMConfigResponse? { + val networkResponse = remoteDataProvider.fetchMMConfigResponse(screenName) + + return networkResponse.data?.let { + executeQuery( + queryName = + upiSpendDatabase.get().transactionsDao()::deleteTransactionsOlderThan.name, + methodName = ::fetchAndSaveConfigResponse.name, + query = { + upiSpendDatabase + .get() + .transactionsDao() + .deleteTransactionsOlderThan( + it.timestampConfig?.twelveMonthsOldTimestamp.orZero() + ) + }, + ) + mmConfigResponseHelper.saveMMConfigToDB(it) + mmConfigResponseHelper.getMMConfig() + } ?: run { null } + } + + private suspend fun syncTransactions(startTimestamp: Long): Boolean { + val transactions = fetchUpiTransactions(startTimestamp) + return if (transactions.isNotEmpty()) { + upiSpendLocalDataSyncManager.insertUpiTransactions(transactions) + true + } else { + false + } + } + + private suspend fun fetchUpiTransactions(startTimestamp: Long): List { + val transactions = mutableListOf() + var fromUpdatedAt = startTimestamp + + while (true) { + when (val response = fetchTransactionPage(fromUpdatedAt)) { + is UpiSpendTransactionsApiCallStatus.Success -> { + transactions += response.data + fromUpdatedAt = response.latestUpdatedAt ?: fromUpdatedAt + if (response.isLastPage.orTrue()) return transactions + } + is UpiSpendTransactionsApiCallStatus.Failure -> return transactions + } + } + } + + private suspend fun fetchTransactionPage( + fromUpdatedAt: Long + ): UpiSpendTransactionsApiCallStatus { + val response = remoteDataProvider.fetchUpiSpendTransactions(fromUpdatedAt) + return if (response.isSuccessWithData()) { + UpiSpendTransactionsApiCallStatus.Success( + response.data?.transactions.orEmpty(), + response.data?.isLastPage, + response.data?.latestUpdatedAt, + ) + } else { + UpiSpendTransactionsApiCallStatus.Failure + } + } +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/helper/AllMonthsDataSyncHelper.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/helper/AllMonthsDataSyncHelper.kt index 136098227b..c0376865a2 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/helper/AllMonthsDataSyncHelper.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/helper/AllMonthsDataSyncHelper.kt @@ -8,7 +8,7 @@ package com.navi.moneymanager.common.datasync.helper import com.navi.base.utils.orZero -import com.navi.moneymanager.common.analytics.DataSyncEventTrackerImpl +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper import com.navi.moneymanager.common.dataprovider.domain.LocalDataSyncManager import com.navi.moneymanager.common.dataprovider.domain.RemoteDataProvider import com.navi.moneymanager.common.datasync.model.DataSyncState @@ -23,8 +23,11 @@ import javax.inject.Inject class AllMonthsDataSyncHelper @Inject -constructor(remoteDataProvider: RemoteDataProvider, localDataSyncManager: LocalDataSyncManager) : - BaseDataSyncHelper(remoteDataProvider, localDataSyncManager) { +constructor( + remoteDataProvider: RemoteDataProvider, + localDataSyncManager: LocalDataSyncManager, + mmConfigResponseHelper: MMConfigResponseHelper, +) : BaseDataSyncHelper(remoteDataProvider, localDataSyncManager, mmConfigResponseHelper) { override suspend fun execute( timestampConfig: TimestampConfig?, @@ -34,14 +37,14 @@ constructor(remoteDataProvider: RemoteDataProvider, localDataSyncManager: LocalD if (twelveMonthsOldTimestamp == 0L) return DataSyncState.Failed val fromTimestamp = localDataSyncManager.getLastSyncedTimestamp(twelveMonthsOldTimestamp) return if (localDataSyncManager.isTotalSyncCompleted()) { - DataSyncEventTrackerImpl.syncStarted(TransactionSyncType.ALL_MONTH_IN_ONE_SHOT.name) + dataSyncEventTracker.syncStarted(TransactionSyncType.ALL_MONTH_IN_ONE_SHOT.name) syncAllDataAndInsertInOneShot( fromTimestamp = fromTimestamp, pageSize = paginationConfig?.pageSize ?: PAGE_SIZE, queryBy = UPDATED_AT, ) } else { - DataSyncEventTrackerImpl.syncStarted(TransactionSyncType.ALL_MONTH_PAGE_WISE.name) + dataSyncEventTracker.syncStarted(TransactionSyncType.ALL_MONTH_PAGE_WISE.name) syncAllDataAndInsertPageWise( fromTimestamp = fromTimestamp, pageSize = paginationConfig?.pageSize ?: PAGE_SIZE, @@ -68,7 +71,7 @@ constructor(remoteDataProvider: RemoteDataProvider, localDataSyncManager: LocalD if (result is DataSyncState.Failed) return result localDataSyncManager.updateCurrentMonthSyncFlag() localDataSyncManager.updateTotalSyncCompleteFlag() - DataSyncEventTrackerImpl.syncSuccess(syncType) + dataSyncEventTracker.syncSuccess(syncType) return DataSyncState.Completed } @@ -93,7 +96,7 @@ constructor(remoteDataProvider: RemoteDataProvider, localDataSyncManager: LocalD if (newTransactionCount > 0) { localDataSyncManager.updateNewTransactionCount(newTransactionCount) } - DataSyncEventTrackerImpl.syncSuccess(syncType) + dataSyncEventTracker.syncSuccess(syncType) return DataSyncState.Completed } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/helper/BaseDataSyncHelper.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/helper/BaseDataSyncHelper.kt index eab49b4e6b..3c18726da7 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/helper/BaseDataSyncHelper.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/helper/BaseDataSyncHelper.kt @@ -9,6 +9,7 @@ package com.navi.moneymanager.common.datasync.helper import com.navi.common.network.models.isSuccessWithData import com.navi.moneymanager.common.analytics.DataSyncEventTrackerImpl +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper import com.navi.moneymanager.common.dataprovider.domain.LocalDataSyncManager import com.navi.moneymanager.common.dataprovider.domain.RemoteDataProvider import com.navi.moneymanager.common.datasync.model.DataSyncState @@ -19,12 +20,20 @@ import com.navi.moneymanager.common.network.model.PaginationConfig import com.navi.moneymanager.common.network.model.SpendGoalData import com.navi.moneymanager.common.network.model.TimestampConfig import com.navi.moneymanager.common.network.model.TransactionData +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE abstract class BaseDataSyncHelper( protected val remoteDataProvider: RemoteDataProvider, protected val localDataSyncManager: LocalDataSyncManager, + protected val mmConfigResponseHelper: MMConfigResponseHelper, ) { + protected val dataSyncEventTracker by lazy { + DataSyncEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + abstract suspend fun execute( timestampConfig: TimestampConfig?, paginationConfig: PaginationConfig?, @@ -81,17 +90,17 @@ abstract class BaseDataSyncHelper( } fun accountsApiFailure(type: String): DataSyncState { - DataSyncEventTrackerImpl.accountSyncFailed(type) + dataSyncEventTracker.accountSyncFailed(type) return DataSyncState.Failed } fun transactionsApiFailure(type: String): DataSyncState { - DataSyncEventTrackerImpl.transactionSyncFailed(type) + dataSyncEventTracker.transactionSyncFailed(type) return DataSyncState.Failed } fun spendGoalsApiFailure(type: String): DataSyncState { - DataSyncEventTrackerImpl.spendGoalsSyncFailed(type) + dataSyncEventTracker.spendGoalsSyncFailed(type) return DataSyncState.Failed } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/helper/CurrentMonthDataSyncHelper.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/helper/CurrentMonthDataSyncHelper.kt index a42717cc89..95f7773c7a 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/helper/CurrentMonthDataSyncHelper.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/helper/CurrentMonthDataSyncHelper.kt @@ -7,7 +7,7 @@ package com.navi.moneymanager.common.datasync.helper -import com.navi.moneymanager.common.analytics.DataSyncEventTrackerImpl +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper import com.navi.moneymanager.common.dataprovider.domain.LocalDataSyncManager import com.navi.moneymanager.common.dataprovider.domain.RemoteDataProvider import com.navi.moneymanager.common.datasync.model.DataSyncState @@ -20,8 +20,11 @@ import javax.inject.Inject class CurrentMonthDataSyncHelper @Inject -constructor(remoteDataProvider: RemoteDataProvider, localDataSyncManager: LocalDataSyncManager) : - BaseDataSyncHelper(remoteDataProvider, localDataSyncManager) { +constructor( + remoteDataProvider: RemoteDataProvider, + localDataSyncManager: LocalDataSyncManager, + mmConfigResponseHelper: MMConfigResponseHelper, +) : BaseDataSyncHelper(remoteDataProvider, localDataSyncManager, mmConfigResponseHelper) { override suspend fun execute( timestampConfig: TimestampConfig?, @@ -30,7 +33,7 @@ constructor(remoteDataProvider: RemoteDataProvider, localDataSyncManager: LocalD val syncType = TransactionSyncType.CURRENT_MONTH.name val startTimestamp = timestampConfig?.currentMonthStartTimestamp ?: 0L if (startTimestamp == 0L) return DataSyncState.Failed - DataSyncEventTrackerImpl.syncStarted(syncType) + dataSyncEventTracker.syncStarted(syncType) val transactions = fetchCurrentMonthTransactions(startTimestamp) ?: return transactionsApiFailure(syncType) @@ -46,7 +49,7 @@ constructor(remoteDataProvider: RemoteDataProvider, localDataSyncManager: LocalD isCurrentMonthData = true, ) localDataSyncManager.updateCurrentMonthSyncFlag() - DataSyncEventTrackerImpl.syncSuccess(syncType) + dataSyncEventTracker.syncSuccess(syncType) return DataSyncState.Completed } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/model/TransactionsApiCallStatus.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/model/TransactionsApiCallStatus.kt index af750f075a..4b3b36aec2 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/model/TransactionsApiCallStatus.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/model/TransactionsApiCallStatus.kt @@ -1,6 +1,6 @@ /* * - * * Copyright © 2024 by Navi Technologies Limited + * * Copyright © 2024-2025 by Navi Technologies Limited * * All rights reserved. Strictly confidential * */ diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/model/UpiSpendTransactionsApiCallStatus.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/model/UpiSpendTransactionsApiCallStatus.kt new file mode 100644 index 0000000000..25f872b0a5 --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/datasync/model/UpiSpendTransactionsApiCallStatus.kt @@ -0,0 +1,20 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.datasync.model + +import com.navi.moneymanager.common.network.model.UpiSpendTransactionData + +sealed class UpiSpendTransactionsApiCallStatus { + data class Success( + val data: List, + val isLastPage: Boolean? = null, + val latestUpdatedAt: Long? = null, + ) : UpiSpendTransactionsApiCallStatus() + + data object Failure : UpiSpendTransactionsApiCallStatus() +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/db/upi/dao/UpiSpendTransactionDao.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/db/upi/dao/UpiSpendTransactionDao.kt new file mode 100644 index 0000000000..0fb6131c59 --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/db/upi/dao/UpiSpendTransactionDao.kt @@ -0,0 +1,68 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.db.upi.dao + +import androidx.room.Dao +import androidx.room.Query +import androidx.room.Upsert +import com.navi.moneymanager.common.db.upi.entity.UpiSpendTransactionEntity +import com.navi.moneymanager.common.model.CategorySummary +import com.navi.moneymanager.common.model.database.UpiTransactionDetails +import com.navi.moneymanager.common.model.database.UpiTransactionSummaryData +import com.navi.moneymanager.common.utils.Constants.UPI_SPEND_TRANSACTION_TABLE +import kotlinx.coroutines.flow.Flow + +@Dao +interface UpiSpendTransactionDao { + @Upsert suspend fun insertAllTransactions(transactions: List) + + @Query( + "SELECT upiTxnId, txnTimestamp, txnMonth, txnYear, txnAmount, type, counterPartyName, finalCategory " + + "FROM $UPI_SPEND_TRANSACTION_TABLE WHERE txnTimestamp BETWEEN :startDate AND :endDate" + ) + fun fetchAllTransactions(startDate: Long, endDate: Long): Flow> + + @Query("SELECT COUNT(*) FROM $UPI_SPEND_TRANSACTION_TABLE") fun getTransactionCount(): Flow + + @Query( + "SELECT upiTxnId, txnTimestamp, txnMonth, txnYear, txnAmount, type, counterPartyName, finalCategory FROM $UPI_SPEND_TRANSACTION_TABLE WHERE finalCategory = :category AND txnTimestamp BETWEEN :startDate AND :endDate " + ) + fun fetchCategorisedTransactions( + category: String, + startDate: Long, + endDate: Long, + ): Flow> + + @Query( + "SELECT upiTxnId, naviUpiTxnId, txnTimestamp, txnAmount, mode, type, counterPartyName, finalCategory " + + "FROM $UPI_SPEND_TRANSACTION_TABLE " + + "WHERE upiTxnId = :transactionId" + ) + fun fetchTransactionDetails(transactionId: String): Flow + + @Query( + " SELECT finalCategory, COUNT(upiTxnId) AS numberOfTransactions, SUM(txnAmount) AS totalAmount " + + "FROM $UPI_SPEND_TRANSACTION_TABLE " + + "WHERE type = 'DEBIT' AND txnMonth = :month AND txnYear = :year " + + "GROUP BY finalCategory" + ) + fun getCategorizedTransactionSummary(month: Int, year: Int): Flow> + + @Query("DELETE FROM $UPI_SPEND_TRANSACTION_TABLE") suspend fun deleteAllTransactions() + + @Query("DELETE FROM $UPI_SPEND_TRANSACTION_TABLE WHERE txnTimestamp <= :timestamp") + suspend fun deleteTransactionsOlderThan(timestamp: Long) + + @Query( + "SELECT COUNT(*) FROM $UPI_SPEND_TRANSACTION_TABLE WHERE txnTimestamp BETWEEN :startDate AND :endDate " + ) + fun getTransactionsCount(startDate: Long, endDate: Long): Flow + + @Query("SELECT MAX(txnTimestamp) FROM $UPI_SPEND_TRANSACTION_TABLE") + fun getLatestTransactionTimestamp(): Flow +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/db/upi/database/UpiSpendDatabase.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/db/upi/database/UpiSpendDatabase.kt new file mode 100644 index 0000000000..521fcb12b6 --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/db/upi/database/UpiSpendDatabase.kt @@ -0,0 +1,19 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.db.upi.database + +import androidx.room.Database +import androidx.room.RoomDatabase +import com.navi.moneymanager.common.db.upi.dao.UpiSpendTransactionDao +import com.navi.moneymanager.common.db.upi.entity.UpiSpendTransactionEntity + +@Database(entities = [UpiSpendTransactionEntity::class], version = 1, exportSchema = false) +abstract class UpiSpendDatabase : RoomDatabase() { + + abstract fun transactionsDao(): UpiSpendTransactionDao +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/db/upi/entity/UpiSpendTransactionEntity.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/db/upi/entity/UpiSpendTransactionEntity.kt new file mode 100644 index 0000000000..f29d4cc47e --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/db/upi/entity/UpiSpendTransactionEntity.kt @@ -0,0 +1,32 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.db.upi.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.navi.moneymanager.common.utils.Constants.UPI_SPEND_TRANSACTION_TABLE + +@Entity(tableName = UPI_SPEND_TRANSACTION_TABLE) +data class UpiSpendTransactionEntity( + @PrimaryKey @ColumnInfo(name = "upiTxnId") val upiTxnId: String, + @ColumnInfo(name = "naviUpiTxnId") val naviUpiTxnId: String, + @ColumnInfo(name = "txnTimestamp") val txnTimestamp: Long?, + @ColumnInfo(name = "txnAmount") val txnAmount: Double?, + @ColumnInfo(name = "valueDate") val valueDate: Long?, + @ColumnInfo(name = "mode") val mode: String?, + @ColumnInfo(name = "type") val type: String?, + @ColumnInfo(name = "counterPartyName") val counterPartyName: String?, + @ColumnInfo(name = "txnDate") val txnDate: String?, + @ColumnInfo(name = "txnDay") val txnDay: Int?, + @ColumnInfo(name = "txnMonth") val txnMonth: Int?, + @ColumnInfo(name = "txnYear") val txnYear: Int?, + @ColumnInfo(name = "finalCategory") val finalCategory: String, + @ColumnInfo(name = "categoryName") val categoryName: String, + @ColumnInfo(name = "updatedAt") val updatedAt: Long?, +) diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/helper/BarGraphHelper.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/helper/BarGraphHelper.kt index 363c40a746..e9db67f66a 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/helper/BarGraphHelper.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/helper/BarGraphHelper.kt @@ -18,6 +18,7 @@ import com.navi.moneymanager.common.model.BarGraphElement import com.navi.moneymanager.common.model.SelectedMonth import com.navi.moneymanager.common.model.YAxisAverageData import com.navi.moneymanager.common.model.database.TransactionSummaryData +import com.navi.moneymanager.common.model.sectionHeader.MMJourneySource import com.navi.moneymanager.common.utils.Constants.DEBIT import com.navi.moneymanager.common.utils.Constants.SELF_TRANSFER import dagger.hilt.android.qualifiers.ApplicationContext @@ -83,6 +84,7 @@ abstract class BarGraphHelper(@ApplicationContext protected val context: Context currentMonth: Int, currentYear: Int, selectedBankReferenceIds: Set, + journeySource: String = MMJourneySource.ACCOUNT_AGGREGATOR.name, ): BarGraphData fun getSpendTransactionsForLastXMonths( diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/helper/UpiSpendAnalysisWidgetHelper.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/helper/UpiSpendAnalysisWidgetHelper.kt new file mode 100644 index 0000000000..62e5b72257 --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/helper/UpiSpendAnalysisWidgetHelper.kt @@ -0,0 +1,83 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.helper + +import com.navi.base.utils.BaseUtils.getDayMonthAndYearFromTimestamp +import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper +import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper.UPI_SPEND_CROSS_VARIANT_TIMEOUT_MILLIS +import com.navi.common.usecase.LitmusExperimentsUseCase +import com.navi.moneymanager.common.dataprovider.data.datastore.DataStoreInfoProvider +import com.navi.moneymanager.common.dataprovider.domain.upi.TxnHistoryUpiSpendDataProvider +import com.navi.moneymanager.common.dataprovider.domain.upi.UpiSpendLocalDataSyncManager +import com.navi.moneymanager.common.model.SelectedMonth +import com.navi.moneymanager.common.model.upi.UpiTxnHistorySpendAnalysisData +import com.navi.moneymanager.common.network.di.RoomDataStoreInfoProvider +import com.navi.moneymanager.common.utils.Constants.CONTROL +import com.navi.moneymanager.common.utils.Constants.LITMUS_EXPERIMENT_TXN_HISTORY_SPEND_ANALYSIS +import com.navi.moneymanager.common.utils.Constants.UPI_SPEND_CROSS_VARIANT_DISMISS_TIMESTAMP +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow +import org.joda.time.DateTime + +class UpiSpendAnalysisWidgetHelper +@Inject +constructor( + private val upiSpendLocalDataSyncManager: UpiSpendLocalDataSyncManager, + private val litmusExperimentsUseCase: LitmusExperimentsUseCase, + @RoomDataStoreInfoProvider private val dbDataStoreProvider: DataStoreInfoProvider, + private val txnHistoryUpiSpendDataProvider: TxnHistoryUpiSpendDataProvider, +) { + suspend fun isTotalSyncCompleted(): Boolean { + return upiSpendLocalDataSyncManager.isTotalSyncCompleted() + } + + suspend fun dismissUpiSpendAnalysisWidget() { + dbDataStoreProvider.saveLongData( + UPI_SPEND_CROSS_VARIANT_DISMISS_TIMESTAMP, + System.currentTimeMillis(), + ) + } + + fun getEntryPointExpVariant(): Flow = flow { + val expResponse = + litmusExperimentsUseCase.execute( + experimentName = LITMUS_EXPERIMENT_TXN_HISTORY_SPEND_ANALYSIS + ) + + val currentVariant = + if (expResponse?.variant?.enabled == true) { + expResponse.variant.name + } else { + CONTROL + } + emit(currentVariant) + } + + suspend fun shouldShowCrossVariant(): Boolean { + val lastDismissTime = + dbDataStoreProvider.getLongData(UPI_SPEND_CROSS_VARIANT_DISMISS_TIMESTAMP, 0L).first() + + return (System.currentTimeMillis() - lastDismissTime) > + FirebaseRemoteConfigHelper.getLong(UPI_SPEND_CROSS_VARIANT_TIMEOUT_MILLIS) + } + + suspend fun getTxnHistoryScreenSummaryData( + shouldShowEntrypoint: Boolean, + latestOrderTimestamp: DateTime?, + ): UpiTxnHistorySpendAnalysisData? { + if (latestOrderTimestamp == null || !shouldShowEntrypoint) return null + val dateComponents = getDayMonthAndYearFromTimestamp(latestOrderTimestamp.millis) + val upiTxnHistorySpendAnalysisData = + txnHistoryUpiSpendDataProvider.getTxnHistoryScreenSummaryData( + SelectedMonth(month = dateComponents.second, dateComponents.third) + ) + return upiTxnHistorySpendAnalysisData + } +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/helper/UpiSpendTransactionsDataHelper.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/helper/UpiSpendTransactionsDataHelper.kt new file mode 100644 index 0000000000..afcbb1e26a --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/helper/UpiSpendTransactionsDataHelper.kt @@ -0,0 +1,58 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.helper + +import android.annotation.SuppressLint +import android.content.Context +import com.navi.base.utils.orElse +import com.navi.base.utils.orZero +import com.navi.moneymanager.R +import com.navi.moneymanager.common.db.upi.entity.UpiSpendTransactionEntity +import com.navi.moneymanager.common.network.model.MMConfigResponse +import com.navi.moneymanager.common.network.model.UpiSpendTransactionData +import com.navi.moneymanager.common.utils.Constants.UNCATEGORIZED +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject + +class UpiSpendTransactionsDataHelper +@Inject +constructor(@ApplicationContext private val context: Context) { + + @SuppressLint("DefaultLocale") + fun getTransactionsEntity( + transactionsList: List, + mmConfig: MMConfigResponse?, + ): List { + return transactionsList.map { + val txnMonth = it.txnDate?.split("-")?.get(1)?.toInt().orZero() - 1 // Month is 0 based + val txnYear = it.txnDate?.split("-")?.get(0)?.toInt() + UpiSpendTransactionEntity( + upiTxnId = it.upiTxnId.orEmpty(), + naviUpiTxnId = it.naviUpiTxnId.orEmpty(), + txnTimestamp = it.txnTimestamp, + txnAmount = String.format("%.2f", it.txnAmount).toDouble(), + valueDate = it.valueDate, + mode = it.mode, + type = it.type, + counterPartyName = it.counterPartyName, + txnDate = it.txnDate, + txnDay = it.txnDate?.split("-")?.get(2)?.toInt(), + txnMonth = txnMonth, + txnYear = txnYear, + finalCategory = it.finalCategory.orElse(UNCATEGORIZED), + categoryName = + mmConfig + ?.categories + ?.find { category -> category.categoryId == it.finalCategory } + ?.categoryName + .orElse(context.resources.getString(R.string.others)), + updatedAt = it.updatedAt, + ) + } + } +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/illustration/repository/ImageRepository.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/illustration/repository/ImageRepository.kt index e4dc2dd251..2fa8106d91 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/illustration/repository/ImageRepository.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/illustration/repository/ImageRepository.kt @@ -57,6 +57,9 @@ class ImageRepository : IllustrationRepository { const val GREEN_RUPEE_FILLED_ICON = "GREEN_RUPEE_FILLED_ICON" const val RED_RUPEE_FILLED_ICON = "RED_RUPEE_FILLED_ICON" const val GREEN_CHECK_ICON = "GREEN_CHECK_ICON" + const val FEEDBACK_ICON = "FEEDBACK_ICON" + const val NAVI_LOGO = "NAVI_LOGO" + const val CROSS_PURPLE = "CROSS_PURPLE" } override fun getResourceId(illustrationName: String, isDarkThemeEnabled: Boolean): Int { @@ -157,6 +160,11 @@ class ImageRepository : IllustrationRepository { "https://public-assets.prod.navi-sa.in/money-manager/svg/common/ic_red_rupee.svg" GREEN_CHECK_ICON -> "https://public-assets.prod.navi-sa.in/money-manager/svg/common/ic_green_check.svg" + FEEDBACK_ICON -> + "https://public-assets.prod.navi-sa.in/money-manager/png/mm_feedback.png" + NAVI_LOGO -> "https://public-assets.prod.navi-sa.in/navi-coin/png/filled_navi_logo.png" + CROSS_PURPLE -> + "https://public-assets.prod.navi-sa.in/money-manager/png/ic_close_purple.png" else -> EMPTY } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/manager/MMLibManager.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/manager/MMLibManager.kt index c636b2cb2f..6f41ddfa33 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/manager/MMLibManager.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/manager/MMLibManager.kt @@ -10,6 +10,7 @@ package com.navi.moneymanager.common.manager import com.navi.base.cache.repository.NaviCacheRepository import com.navi.base.sharedpref.PreferenceManager import com.navi.moneymanager.common.db.database.MMDatabase +import com.navi.moneymanager.common.db.upi.database.UpiSpendDatabase import com.navi.moneymanager.common.utils.Constants.MM_IS_USER_ONBOARDED import com.navi.moneymanager.common.utils.DbCacheConstants.MONEY_MANAGER_CONFIG_RESPONSE_KEY import com.navi.moneymanager.common.utils.DbCacheConstants.MONEY_MANAGER_VALUE_PROPOSITION_SCREEN_KEY @@ -25,18 +26,26 @@ class MMLibManager @Inject constructor( private val mmDatabase: Lazy, + private val upiSpendDatabase: Lazy, private val naviCacheRepository: NaviCacheRepository, ) { fun initMoneyManagerDB() { CoroutineScope(Dispatchers.IO).launch { mmDatabase.get().accountsDao().getAccountsCount() } } + fun initUpiSpendAnalyserDB() { + CoroutineScope(Dispatchers.IO).launch { + upiSpendDatabase.get().transactionsDao().getTransactionCount() + } + } + fun clearMoneyManagerData() { CoroutineScope(Dispatchers.IO).launch { PreferenceManager.setBooleanPreference(MM_IS_USER_ONBOARDED, false) naviCacheRepository.clear(key = MONEY_MANAGER_VALUE_PROPOSITION_SCREEN_KEY) naviCacheRepository.clear(key = MONEY_MANAGER_CONFIG_RESPONSE_KEY) mmDatabase.get().clearAllTables() + upiSpendDatabase.get().clearAllTables() } } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/DailySpendBottomSheetData.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/DailySpendBottomSheetData.kt index 7efa7dacf6..f0fa04a6b6 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/DailySpendBottomSheetData.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/DailySpendBottomSheetData.kt @@ -13,6 +13,7 @@ data class DailySpendBottomSheetData( val headerData: DailySpendBottomSheetHeaderData, val transactions: List? = null, val ctaText: String, + val isFooterVisible: Boolean = true, ) data class DailySpendBottomSheetHeaderData( diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/MMToastData.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/MMToastData.kt new file mode 100644 index 0000000000..9d01182feb --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/MMToastData.kt @@ -0,0 +1,10 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.model + +data class MMToastData(val isVisible: Boolean = false, val message: String? = null) diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/SpendCategorizationLoaded.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/SpendCategorizationLoaded.kt index 428e9f2d48..81ce50ba9b 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/SpendCategorizationLoaded.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/SpendCategorizationLoaded.kt @@ -67,8 +67,7 @@ sealed class TotalSpends { val iconUrl: IllustrationSource, override val title: String, val subtitle: String, - val spendGoal: Double? = null, - val progress: Double? = null, + val spendGoalState: SpendGoalState = SpendGoalState.None, val actionIcon: IllustrationSource? = null, ) : TotalSpends() @@ -76,8 +75,7 @@ sealed class TotalSpends { val iconUrl: IllustrationSource, override val title: String, val subtitle: String, - val spendGoal: Double? = null, - val progress: Double? = null, + val spendGoalState: SpendGoalState = SpendGoalState.None, val actionIcon: IllustrationSource? = null, ) : TotalSpends() @@ -85,6 +83,7 @@ sealed class TotalSpends { override val title: String, val lottieUrl: IllustrationSource, val animationUrl: IllustrationSource, + val spendGoalState: SpendGoalState = SpendGoalState.None, ) : TotalSpends() } @@ -116,6 +115,15 @@ sealed class SpendCategoryItemData { ) : SpendCategoryItemData() } +sealed class SpendGoalState { + data class Loaded(val spendGoal: Double? = null, val progress: Double? = null) : + SpendGoalState() + + data object Loading : SpendGoalState() + + data object None : SpendGoalState() +} + sealed class SpendCategorizationAction { data class ViewTotalSpends(val selectedMonth: SelectedMonth) : SpendCategorizationAction() diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/Transaction.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/Transaction.kt index 7696ee1f5a..9df499dec0 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/Transaction.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/Transaction.kt @@ -26,4 +26,5 @@ data class Transaction( val counterPartyName: String, val type: String, val txnTimestamp: Long, + val isCategoryClickable: Boolean = true, ) diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/bottomSheet/DashboardScreenBottomSheets.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/bottomSheet/DashboardScreenBottomSheets.kt index 4df53109e5..4d9a4d3f85 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/bottomSheet/DashboardScreenBottomSheets.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/bottomSheet/DashboardScreenBottomSheets.kt @@ -10,6 +10,7 @@ package com.navi.moneymanager.common.model.bottomSheet import com.navi.moneymanager.common.model.DataLoadingBottomSheetData import com.navi.moneymanager.common.model.MonthSelectionBottomSheetData import com.navi.moneymanager.postonboard.dashboard.model.DashboardScreenUiEvent +import com.navi.moneymanager.postonboard.dashboard.model.FeedBackBottomSheetData import com.navi.moneymanager.postonboard.dashboard.model.FinarkeinErrorBottomSheetData import com.navi.moneymanager.postonboard.dashboard.model.OnboardingStatus import com.navi.moneymanager.postonboard.help.model.HelpBottomSheetState @@ -119,4 +120,15 @@ sealed class DashboardScreenBottomSheets( ), ), ) + + data class FeedbackBottomSheet(val data: FeedBackBottomSheetData? = null) : + DashboardScreenBottomSheets( + bottomSheetContent = data, + bottomSheetConfig = + BottomSheetConfig( + isCancellable = true, + dismissEvent = + DashboardScreenUiEvent.DismissBottomSheet(FeedbackBottomSheet::class.java), + ), + ) } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/database/UpiTransactionDetails.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/database/UpiTransactionDetails.kt new file mode 100644 index 0000000000..0c3696b82c --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/database/UpiTransactionDetails.kt @@ -0,0 +1,19 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.model.database + +data class UpiTransactionDetails( + val upiTxnId: String, + val naviUpiTxnId: String, + val txnTimestamp: Long?, + val txnAmount: Double?, + val mode: String?, + val type: String?, + val counterPartyName: String?, + val finalCategory: String, +) diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/database/UpiTransactionSummaryData.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/database/UpiTransactionSummaryData.kt new file mode 100644 index 0000000000..5710177b8a --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/database/UpiTransactionSummaryData.kt @@ -0,0 +1,36 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.model.database + +data class UpiTransactionSummaryData( + val upiTxnId: String, + val txnTimestamp: Long?, + val txnMonth: Int?, + val txnYear: Int?, + val txnAmount: Double?, + val type: String?, + val counterPartyName: String?, + val finalCategory: String, + val mode: String?, +) + +fun UpiTransactionSummaryData.toTransactionSummaryData(): TransactionSummaryData { + return TransactionSummaryData( + txnId = upiTxnId, + linkedAccRef = null, + txnTimestamp = txnTimestamp, + txnMonth = txnMonth, + txnYear = txnYear, + txnAmount = txnAmount, + type = type, + counterPartyName = counterPartyName, + finalCategory = finalCategory, + mode = mode, + narration = null, + ) +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/sectionHeader/JourneySource.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/sectionHeader/JourneySource.kt new file mode 100644 index 0000000000..8858b8c416 --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/sectionHeader/JourneySource.kt @@ -0,0 +1,13 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.model.sectionHeader + +enum class MMJourneySource { + ACCOUNT_AGGREGATOR, + NAVI_UPI, +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/upi/UpiTxnHistorySpendAnalysisData.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/upi/UpiTxnHistorySpendAnalysisData.kt new file mode 100644 index 0000000000..98d7833791 --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/model/upi/UpiTxnHistorySpendAnalysisData.kt @@ -0,0 +1,30 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.model.upi + +import androidx.compose.ui.graphics.Color +import com.navi.moneymanager.common.illustration.model.IllustrationSource +import com.navi.moneymanager.common.model.SelectedMonth + +data class UpiTxnHistorySpendAnalysisData( + val title: String, + val subTitle: String? = null, + val spendCategoryItem: TopSpendCategoryItemData? = null, + val ctaText: String, + val showCrossIcon: Boolean = false, + val selectedMonth: SelectedMonth, +) + +data class TopSpendCategoryItemData( + val name: String, + val iconUrl: IllustrationSource, + val progress: Float, + val progressColor: Color, + val amount: String, + val progressText: String, +) diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/network/di/NetworkModule.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/network/di/NetworkModule.kt index 639e3dc916..d82ad7888d 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/network/di/NetworkModule.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/network/di/NetworkModule.kt @@ -21,9 +21,11 @@ import com.navi.common.utils.registerUiTronSerializer import com.navi.moneymanager.common.dataprovider.data.datastore.DataStoreInfoProvider import com.navi.moneymanager.common.dataprovider.data.datastore.DbDataStoreProvider import com.navi.moneymanager.common.db.database.MMDatabase +import com.navi.moneymanager.common.db.upi.database.UpiSpendDatabase import com.navi.moneymanager.common.network.HttpClient import com.navi.moneymanager.common.network.service.RetrofitService import com.navi.moneymanager.common.utils.Constants.MM_DB_NAME +import com.navi.moneymanager.common.utils.Constants.UPI_SPEND_DB_NAME import com.navi.moneymanager.common.utils.DateTimeConverterAdapter import dagger.Module import dagger.Provides @@ -99,6 +101,13 @@ object MMNetworkModule { return Room.databaseBuilder(context, MMDatabase::class.java, MM_DB_NAME).build() } + @Singleton + @Provides + fun providesUpiSpendDatabase(@ApplicationContext context: Context): UpiSpendDatabase { + return Room.databaseBuilder(context, UpiSpendDatabase::class.java, UPI_SPEND_DB_NAME) + .build() + } + @Singleton @Provides @RoomDataStoreInfoProvider diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/network/model/TransactionResponse.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/network/model/TransactionResponse.kt index 1fdfe30ad1..02999a7c52 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/network/model/TransactionResponse.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/network/model/TransactionResponse.kt @@ -1,6 +1,6 @@ /* * - * * Copyright © 2024 by Navi Technologies Limited + * * Copyright © 2024-2025 by Navi Technologies Limited * * All rights reserved. Strictly confidential * */ diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/network/model/UpiSpendTransactionData.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/network/model/UpiSpendTransactionData.kt new file mode 100644 index 0000000000..998c04b0dc --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/network/model/UpiSpendTransactionData.kt @@ -0,0 +1,22 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.network.model + +data class UpiSpendTransactionData( + val upiTxnId: String? = null, + val naviUpiTxnId: String? = null, + val txnTimestamp: Long? = null, + val txnAmount: Double? = null, + val valueDate: Long? = null, + val mode: String? = null, + val type: String? = null, + val counterPartyName: String? = null, + val txnDate: String?, + val finalCategory: String? = null, + val updatedAt: Long? = null, +) diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/network/model/UpiSpendTransactionResponse.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/network/model/UpiSpendTransactionResponse.kt new file mode 100644 index 0000000000..374aa75d1b --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/network/model/UpiSpendTransactionResponse.kt @@ -0,0 +1,14 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.network.model + +data class UpiSpendTransactionResponse( + val transactions: List? = null, + val isLastPage: Boolean? = null, + val latestUpdatedAt: Long? = null, +) diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/network/service/RetrofitService.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/network/service/RetrofitService.kt index f8095c7ace..ef6b5c9c17 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/network/service/RetrofitService.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/network/service/RetrofitService.kt @@ -23,6 +23,7 @@ import com.navi.moneymanager.common.network.model.SpendGoalData import com.navi.moneymanager.common.network.model.SpendGoalRequest import com.navi.moneymanager.common.network.model.SpendGoalsResponse import com.navi.moneymanager.common.network.model.TransactionResponse +import com.navi.moneymanager.common.network.model.UpiSpendTransactionResponse import com.navi.moneymanager.postonboard.monthlysummary.model.PostCategoryTransactionData import retrofit2.Response import retrofit2.http.Body @@ -110,4 +111,10 @@ interface RetrofitService { suspend fun deleteGoal( @Path("goalId") goalId: Int ): Response> + + @RetryPolicy + @GET("/money-manager/core/transactions/navi-upi") + suspend fun fetchUpiSpendTransactions( + @Query("fromUpdatedAt") fromUpdatedAt: Long + ): Response> } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/MMProgressBar.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/MMProgressBar.kt index 694553c153..f7c977060b 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/MMProgressBar.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/MMProgressBar.kt @@ -27,7 +27,6 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier @@ -46,7 +45,7 @@ import kotlin.math.max @Composable fun MMCategoryProgressBar(progress: Float, color: Color, totalWidth: Dp = 200.dp) { - var animationTriggered by remember { mutableStateOf(false) } + var animationTriggered by rememberSaveable { mutableStateOf(false) } val animatedProgress by animateFloatAsState( targetValue = if (animationTriggered) progress else 0f, diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/MMSearchAndFilter.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/MMSearchAndFilter.kt index a6cf225272..0ff8c1c3e7 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/MMSearchAndFilter.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/MMSearchAndFilter.kt @@ -44,6 +44,7 @@ fun MMSearchAndFilter( placeholderText: String, isFilterApplied: Boolean, onFilterClick: () -> Unit, + getEventTracker: () -> TransactionHistoryEventTrackerImpl, ) { Row( modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 16.dp), @@ -53,7 +54,7 @@ fun MMSearchAndFilter( modifier = Modifier.fillMaxWidth().weight(1f), value = searchQuery, onValueChange = { - TransactionHistoryEventTrackerImpl.onTransactionHistorySearchQueryChanged(it.text) + getEventTracker().onTransactionHistorySearchQueryChanged(it.text) onQueryChange(it) }, placeholderText = placeholderText, diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/barGraph/MMBarGraph.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/barGraph/MMBarGraph.kt index 01ff1a8136..a7edf87787 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/barGraph/MMBarGraph.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/barGraph/MMBarGraph.kt @@ -34,13 +34,11 @@ import androidx.compose.ui.unit.dp import com.navi.base.utils.orFalse import com.navi.base.utils.orZero import com.navi.elex.theme.elexColors -import com.navi.moneymanager.common.analytics.CategoryDetailsEventTrackerImpl -import com.navi.moneymanager.common.analytics.SpendAnalysisEventTrackerImpl +import com.navi.moneymanager.common.analytics.MMBarGraphEventTracker import com.navi.moneymanager.common.illustration.model.IllustrationSource import com.navi.moneymanager.common.model.BarGraphData import com.navi.moneymanager.common.model.BarGraphPageData import com.navi.moneymanager.common.model.SelectedMonth -import com.navi.moneymanager.common.navigation.utils.MMScreen import com.navi.moneymanager.common.ui.composable.barGraph.BarGraphLayoutProperties.BAR_WIDTH import com.navi.moneymanager.common.ui.composable.barGraph.BarGraphLayoutProperties.HORIZONTAL_PADDING import com.navi.moneymanager.common.ui.composable.barGraph.BarGraphLayoutProperties.LINE_PADDING_FOR_TOOL_TIP @@ -59,18 +57,10 @@ internal fun MMBarGraph( onAverageInfoClick: (String?) -> Unit, onBarGraphElementClicked: (SelectedMonth) -> Unit, onBarGraphClicked: () -> Unit = {}, + getEventTracker: () -> MMBarGraphEventTracker, ) { - LaunchedEffect(Unit) { - when (screenName) { - MMScreen.SPEND_ANALYSIS.screen -> { - SpendAnalysisEventTrackerImpl.onSpendAnalysisScreenGraphViewed() - } - MMScreen.CATEGORY_DETAILS.screen -> { - CategoryDetailsEventTrackerImpl.onCategoryDetailsGraphView() - } - } - } + LaunchedEffect(Unit) { getEventTracker().onBarGraphViewed(screenName) } var selectedBar by remember(barGraphData.selectedBar) { mutableStateOf(barGraphData.selectedBar) } @@ -84,18 +74,7 @@ internal fun MMBarGraph( selectedBar = selectedBar, data = pageData, onBarSelected = { monthIndex, clickedBar -> - when (screenName) { - MMScreen.SPEND_ANALYSIS.screen -> { - SpendAnalysisEventTrackerImpl.onSpendAnalysisScreenGraphBarClicked( - monthIndex - ) - } - MMScreen.CATEGORY_DETAILS.screen -> { - CategoryDetailsEventTrackerImpl.onCategoryDetailsGraphBarClicked( - monthIndex - ) - } - } + getEventTracker().onBarGraphClicked(monthIndex, screenName) if (barGraphData.isTotalSyncCompleted.orFalse()) { selectedBar = clickedBar pageData.elementList diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/base/MMTextField.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/base/MMTextField.kt index ec92eaece6..98db31e020 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/base/MMTextField.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/base/MMTextField.kt @@ -51,6 +51,8 @@ fun MMTextField( suffixIllustration: IllustrationType? = null, onSuffixIllustrationClick: () -> Unit, enabled: Boolean = true, + singleLine: Boolean = true, + maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, visualTransformation: VisualTransformation = VisualTransformation.None, textStyle: TextStyle = TextStyle( @@ -66,7 +68,6 @@ fun MMTextField( val focusManager = LocalFocusManager.current val interactionSource = remember { MutableInteractionSource() } - val singleLine = true BasicTextField( value = value, @@ -77,6 +78,7 @@ fun MMTextField( keyboardOptions = keyboardOptions, keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), singleLine = singleLine, + maxLines = maxLines, visualTransformation = visualTransformation, interactionSource = interactionSource, cursorBrush = SolidColor(elexColors.interactive.text.gray.standard), diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/bottomSheet/AverageInfoBottomSheet.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/bottomSheet/AverageInfoBottomSheet.kt index c9529c1e35..4b8f8c1a3d 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/bottomSheet/AverageInfoBottomSheet.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/bottomSheet/AverageInfoBottomSheet.kt @@ -25,39 +25,22 @@ import com.navi.design.font.FontWeightEnum import com.navi.elex.atoms.ElexButton import com.navi.elex.theme.elexColors import com.navi.moneymanager.R -import com.navi.moneymanager.common.analytics.CategoryDetailsEventTrackerImpl -import com.navi.moneymanager.common.analytics.SpendAnalysisEventTrackerImpl -import com.navi.moneymanager.common.navigation.utils.MMScreen +import com.navi.moneymanager.common.analytics.AverageInfoBottomSheetEventTracker import com.navi.moneymanager.common.ui.composable.base.MMText import com.navi.moneymanager.common.utils.fourDpRoundedShape @Composable -fun AverageInfoBottomSheetContent(screenName: String, averageValue: String, onDismiss: () -> Unit) { +fun AverageInfoBottomSheetContent( + screenName: String, + averageValue: String, + onDismiss: () -> Unit, + getEventTracker: () -> AverageInfoBottomSheetEventTracker, +) { - LaunchedEffect(Unit) { - when (screenName) { - MMScreen.SPEND_ANALYSIS.screen -> { - SpendAnalysisEventTrackerImpl.onSpendAnalysisScreenAverageInfoBottomSheetAppeared() - } - MMScreen.CATEGORY_DETAILS.screen -> { - CategoryDetailsEventTrackerImpl.onCategoryDetailsAverageInfoBottomSheetAppeared() - } - } - } + LaunchedEffect(Unit) { getEventTracker().onAverageInfoBottomSheetAppeared(screenName) } DisposableEffect(Unit) { - onDispose { - when (screenName) { - MMScreen.SPEND_ANALYSIS.screen -> { - SpendAnalysisEventTrackerImpl - .onSpendAnalysisScreenAverageInfoBottomSheetDisappeared() - } - MMScreen.CATEGORY_DETAILS.screen -> { - CategoryDetailsEventTrackerImpl - .onCategoryDetailsAverageInfoBottomSheetDisappeared() - } - } - } + onDispose { getEventTracker().onAverageInfoBottomSheetDisappeared(screenName) } } Column( diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/bottomSheet/MonthSelectionBottomSheet.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/bottomSheet/MonthSelectionBottomSheet.kt index 9c49ea5d26..d84f824d40 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/bottomSheet/MonthSelectionBottomSheet.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/bottomSheet/MonthSelectionBottomSheet.kt @@ -34,11 +34,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.navi.design.font.FontWeightEnum import com.navi.elex.theme.elexColors -import com.navi.moneymanager.common.analytics.CategoryDetailsEventTrackerImpl -import com.navi.moneymanager.common.analytics.DashboardEventTrackerImpl -import com.navi.moneymanager.common.analytics.SpendAnalysisEventTrackerImpl +import com.navi.moneymanager.common.analytics.MonthlySelectionBottomSheetEventTracker import com.navi.moneymanager.common.model.MonthSelectionBottomSheetData -import com.navi.moneymanager.common.navigation.utils.MMScreen import com.navi.moneymanager.common.ui.composable.RadioButtonSelectionRow import com.navi.moneymanager.common.ui.composable.base.MMDivider import com.navi.moneymanager.common.ui.composable.base.MMImage @@ -55,37 +52,12 @@ fun MonthSelectionBottomSheetUI( data: MonthSelectionBottomSheetData, onDismiss: () -> Unit, onMonthSelected: (Pair) -> Unit, + getEventTracker: () -> MonthlySelectionBottomSheetEventTracker, ) { - LaunchedEffect(Unit) { - when (screenName) { - MMScreen.DASHBOARD.screen -> { - DashboardEventTrackerImpl.onDashboardMonthSelectionBottomSheetAppeared() - } - MMScreen.SPEND_ANALYSIS.screen -> { - SpendAnalysisEventTrackerImpl.onSpendAnalysisMonthSelectionBottomSheetAppeared() - } - MMScreen.CATEGORY_DETAILS.screen -> { - CategoryDetailsEventTrackerImpl.onCategoryDetailsMonthSelectionBottomSheetAppeared() - } - } - } + LaunchedEffect(Unit) { getEventTracker().onMonthlySelectionBottomSheetAppeared(screenName) } DisposableEffect(Unit) { - onDispose { - when (screenName) { - MMScreen.DASHBOARD.screen -> { - DashboardEventTrackerImpl.onDashboardMonthSelectionBottomSheetDisappeared() - } - MMScreen.SPEND_ANALYSIS.screen -> { - SpendAnalysisEventTrackerImpl - .onSpendAnalysisMonthSelectionBottomSheetDisappeared() - } - MMScreen.CATEGORY_DETAILS.screen -> { - CategoryDetailsEventTrackerImpl - .onCategoryDetailsMonthSelectionBottomSheetDisappeared() - } - } - } + onDispose { getEventTracker().onMonthlySelectionBottomSheetDisappeared(screenName) } } var currentSelection by remember { mutableStateOf(data.selectedMonth) } @@ -163,6 +135,7 @@ fun MonthSelectionBottomSheetUI( onMonthSelected, currentSelection, onDismiss, + getEventTracker = getEventTracker, ) }, ) @@ -192,22 +165,9 @@ private fun onItemClick( onMonthSelected: (Pair) -> Unit, currentSelection: Pair, onDismiss: () -> Unit, + getEventTracker: () -> MonthlySelectionBottomSheetEventTracker, ) { - when (screenName) { - MMScreen.DASHBOARD.screen -> { - DashboardEventTrackerImpl.onDashboardMonthSelectionApplied(currentlySelectedMonthIndex) - } - MMScreen.SPEND_ANALYSIS.screen -> { - SpendAnalysisEventTrackerImpl.onSpendAnalysisScreenMonthSelectionApplied( - currentlySelectedMonthIndex - ) - } - MMScreen.CATEGORY_DETAILS.screen -> { - CategoryDetailsEventTrackerImpl.onCategoryDetailsMonthSelectionApplied( - currentlySelectedMonthIndex - ) - } - } + getEventTracker().onMonthSelectionApplied(currentlySelectedMonthIndex, screenName) onMonthSelected(currentSelection) onDismiss() } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/spendCategoriztion/CategoryTotalSpendSection.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/spendCategoriztion/CategoryTotalSpendSection.kt index 3ef5994392..b3aafe21c2 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/spendCategoriztion/CategoryTotalSpendSection.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/spendCategoriztion/CategoryTotalSpendSection.kt @@ -23,7 +23,6 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -31,19 +30,19 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.navi.base.utils.toAbbreviatedINRFormat -import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper import com.navi.common.utils.onClickWithDebounce import com.navi.design.font.FontWeightEnum import com.navi.elex.theme.elexColors +import com.navi.moneymanager.common.analytics.SpendAnalysisEventTrackerImpl import com.navi.moneymanager.common.illustration.model.IllustrationSource import com.navi.moneymanager.common.illustration.model.IllustrationType import com.navi.moneymanager.common.illustration.model.ImageProperties import com.navi.moneymanager.common.illustration.ui.Illustration import com.navi.moneymanager.common.model.MMUIState +import com.navi.moneymanager.common.model.SpendGoalState import com.navi.moneymanager.common.model.TotalSpends import com.navi.moneymanager.common.ui.composable.base.MMText import com.navi.moneymanager.common.utils.Constants.SLASH_WITH_ADJACENT_SPACES -import com.navi.moneymanager.common.utils.TotalSpendsEmptyToLoaded import com.navi.moneymanager.postonboard.spendgoal.ui.CategoryTotalSpendsSpendGoalSection import com.navi.moneymanager.preonboard.launcher.model.ProgressBarBottomSheetData @@ -56,10 +55,8 @@ fun CategoryTotalSpendsSection( onProgressBarClick: ((ProgressBarBottomSheetData) -> Unit)? = null, onSetGoalCardClick: (() -> Unit)? = null, screenName: String, + getEventTracker: () -> SpendAnalysisEventTrackerImpl, ) { - val isGoalsEnabled = remember { - FirebaseRemoteConfigHelper.getBoolean(FirebaseRemoteConfigHelper.MM_SPEND_GOALS_ENABLED) - } totalSpendsSectionData?.let { data -> when (mmUIState) { MMUIState.Empty -> { @@ -70,7 +67,7 @@ fun CategoryTotalSpendsSection( onProgressBarClick = onProgressBarClick, onSetGoalCardClick = onSetGoalCardClick, screenName = screenName, - isGoalsEnabled = isGoalsEnabled, + goalEventTracker = getEventTracker, ) } @@ -82,7 +79,7 @@ fun CategoryTotalSpendsSection( onProgressBarClick = onProgressBarClick, onSetGoalCardClick = onSetGoalCardClick, screenName = screenName, - isGoalsEnabled = isGoalsEnabled, + goalEventTracker = getEventTracker, ) } @@ -101,7 +98,7 @@ fun TotalSpendsLoadedSection( onProgressBarClick: ((ProgressBarBottomSheetData) -> Unit)? = null, onSetGoalCardClick: (() -> Unit)? = null, screenName: String, - isGoalsEnabled: Boolean, + goalEventTracker: () -> SpendAnalysisEventTrackerImpl, ) { LaunchedEffect(Unit) {} @@ -174,19 +171,24 @@ fun TotalSpendsLoadedSection( fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD, lineHeight = 24.sp, ) - if (isGoalsEnabled) { - data.spendGoal?.let { - MMText( - text = - SLASH_WITH_ADJACENT_SPACES + - data.spendGoal.toAbbreviatedINRFormat(), - modifier = Modifier.padding(top = 6.dp), - color = elexColors.base.text.gray.dim, - fontSize = 14.sp, - fontWeight = FontWeightEnum.NAVI_BODY_REGULAR, - lineHeight = 24.sp, - ) + when (data.spendGoalState) { + is SpendGoalState.Loaded -> { + data.spendGoalState.spendGoal?.let { + MMText( + text = + SLASH_WITH_ADJACENT_SPACES + + data.spendGoalState.spendGoal + .toAbbreviatedINRFormat(), + modifier = Modifier.padding(top = 6.dp), + color = elexColors.base.text.gray.dim, + fontSize = 14.sp, + fontWeight = FontWeightEnum.NAVI_BODY_REGULAR, + lineHeight = 24.sp, + ) + } } + + else -> {} } } } @@ -194,14 +196,19 @@ fun TotalSpendsLoadedSection( data.actionIcon?.let { TotalSpendActionIcon(actionIcon = it) } } } - if (isGoalsEnabled) { - CategoryTotalSpendsSpendGoalSection( - data = data, - isCurrentMonth = isCurrentMonth, - onProgressBarClick = onProgressBarClick, - onSetGoalCardClick = onSetGoalCardClick, - screenName = screenName, - ) + when (data.spendGoalState) { + is SpendGoalState.Loaded -> + CategoryTotalSpendsSpendGoalSection( + totalSpendAmount = data.subtitle, + data = data.spendGoalState, + isCurrentMonth = isCurrentMonth, + onProgressBarClick = onProgressBarClick, + onSetGoalCardClick = onSetGoalCardClick, + screenName = screenName, + goalEventTracker = goalEventTracker, + ) + + else -> {} } } } @@ -215,7 +222,7 @@ fun TotalSpendsEmptySection( onProgressBarClick: ((ProgressBarBottomSheetData) -> Unit)? = null, onSetGoalCardClick: (() -> Unit)? = null, screenName: String, - isGoalsEnabled: Boolean, + goalEventTracker: () -> SpendAnalysisEventTrackerImpl, ) { Box( modifier = @@ -278,33 +285,44 @@ fun TotalSpendsEmptySection( fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD, lineHeight = 24.sp, ) - if (isGoalsEnabled) { - data.spendGoal?.let { - MMText( - text = - SLASH_WITH_ADJACENT_SPACES + - data.spendGoal.toAbbreviatedINRFormat(), - modifier = Modifier.padding(top = 6.dp), - color = elexColors.base.text.gray.dim, - fontSize = 14.sp, - fontWeight = FontWeightEnum.NAVI_BODY_REGULAR, - lineHeight = 24.sp, - ) + when (data.spendGoalState) { + is SpendGoalState.Loaded -> { + data.spendGoalState.spendGoal?.let { + MMText( + text = + SLASH_WITH_ADJACENT_SPACES + + data.spendGoalState.spendGoal + .toAbbreviatedINRFormat(), + modifier = Modifier.padding(top = 6.dp), + color = elexColors.base.text.gray.dim, + fontSize = 14.sp, + fontWeight = FontWeightEnum.NAVI_BODY_REGULAR, + lineHeight = 24.sp, + ) + } } + + else -> {} } } } } data.actionIcon?.let { TotalSpendActionIcon(actionIcon = it) } } - if (isGoalsEnabled) { - CategoryTotalSpendsSpendGoalSection( - data = TotalSpendsEmptyToLoaded(data), - isCurrentMonth = isCurrentMonth, - onProgressBarClick = onProgressBarClick, - onSetGoalCardClick = onSetGoalCardClick, - screenName = screenName, - ) + when (data.spendGoalState) { + is SpendGoalState.Loaded -> { + CategoryTotalSpendsSpendGoalSection( + totalSpendAmount = data.subtitle, + data = data.spendGoalState, + isCurrentMonth = isCurrentMonth, + onProgressBarClick = onProgressBarClick, + onSetGoalCardClick = onSetGoalCardClick, + screenName = screenName, + goalEventTracker = goalEventTracker, + ) + } + + else -> {} } } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/spendCategoriztion/SpendCategorizationSection.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/spendCategoriztion/SpendCategorizationSection.kt index 631975776e..9ad2e338ca 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/spendCategoriztion/SpendCategorizationSection.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/spendCategoriztion/SpendCategorizationSection.kt @@ -34,7 +34,7 @@ import com.navi.design.font.FontWeightEnum import com.navi.design.font.getFontWeight import com.navi.design.font.naviFontFamily import com.navi.elex.theme.elexColors -import com.navi.moneymanager.common.analytics.DashboardEventTrackerImpl +import com.navi.moneymanager.common.analytics.SpendAnalysisEventTrackerImpl import com.navi.moneymanager.common.composable.bankselectionbottomsheet.ZeroTransactionView import com.navi.moneymanager.common.model.MMUIState import com.navi.moneymanager.common.model.SelectedMonth @@ -46,13 +46,15 @@ import com.navi.moneymanager.common.ui.composable.base.MMText import com.navi.moneymanager.common.ui.composable.sectionHeaders.SectionHeader import com.navi.moneymanager.common.utils.fourDpRoundedShape import com.navi.moneymanager.common.utils.isCurrentMonth +import com.navi.moneymanager.postonboard.dashboard.model.AddAccountState @Composable fun SpendCategorizationSection( screenName: String, - isAddAccountSelected: Boolean, + addAccountState: AddAccountState, spendCategorizationState: SpendCategorizationState?, spendCategorizationAction: (SpendCategorizationAction) -> Unit, + getEventTracker: () -> SpendAnalysisEventTrackerImpl, ) { if (spendCategorizationState == null) return when (spendCategorizationState) { @@ -61,6 +63,7 @@ fun SpendCategorizationSection( screenName = screenName, data = spendCategorizationState, spendCategorizationAction = spendCategorizationAction, + getEventTracker = getEventTracker, ) } @@ -69,6 +72,7 @@ fun SpendCategorizationSection( screenName = screenName, data = spendCategorizationState, spendCategorizationAction = spendCategorizationAction, + getEventTracker = getEventTracker, ) } @@ -77,10 +81,11 @@ fun SpendCategorizationSection( SpendAnalysisSpendCategorizationEmptyUI(data = spendCategorizationState) } else { DashboardSpendCategorizationEmptyUI( - isAddAccountSelected = isAddAccountSelected, + addAccountState = addAccountState, spendCategorizationAction = spendCategorizationAction, data = spendCategorizationState, screenName = screenName, + getEventTracker = { getEventTracker() as SpendAnalysisEventTrackerImpl }, ) SelfTransferCategory(spendCategorizationState.selfTransferCategory) { spendCategorizationAction( @@ -103,6 +108,7 @@ fun SpendCategorizationLoadingUI( screenName: String, spendCategorizationAction: (SpendCategorizationAction) -> Unit, data: SpendCategorizationState.Loading, + getEventTracker: () -> SpendAnalysisEventTrackerImpl, ) { SpendCategorizationContentWrapper( onMonthChangeClick = { @@ -115,18 +121,25 @@ fun SpendCategorizationLoadingUI( mmUIState = MMUIState.Loading, isCurrentMonth = isCurrentMonth(data.selectedMonth), screenName = screenName, + getEventTracker = getEventTracker, ) SpendCategoriesHeaderTitle(data.categoryHeaderTitlePrefix, data.categoryHeaderTitleSuffix) - SpendCategoryList(screenName, data.categories, mmUIState = MMUIState.Loading) + SpendCategoryList( + screenName, + data.categories, + mmUIState = MMUIState.Loading, + getEventTracker = getEventTracker, + ) } } @Composable fun DashboardSpendCategorizationEmptyUI( - isAddAccountSelected: Boolean, + addAccountState: AddAccountState, spendCategorizationAction: (SpendCategorizationAction) -> Unit, data: SpendCategorizationState.Empty, screenName: String, + getEventTracker: () -> SpendAnalysisEventTrackerImpl, ) { SpendCategorizationContentWrapper( onMonthChangeClick = { @@ -154,9 +167,10 @@ fun DashboardSpendCategorizationEmptyUI( spendCategorizationAction(SpendCategorizationAction.SetSpendGoal) }, screenName = screenName, + getEventTracker = getEventTracker, ) SpendCategoriesHeaderTitle(data.categoryHeaderTitlePrefix, data.categoryHeaderTitleSuffix) - SpendCategoryEmptyList(isAddAccountSelected = isAddAccountSelected, data = data) { + SpendCategoryEmptyList(addAccountState = addAccountState, data = data) { isTotalSyncCompleted -> spendCategorizationAction( SpendCategorizationAction.AddNewBankAccount(isTotalSyncCompleted) @@ -180,6 +194,7 @@ fun SpendCategorizationLoadedUI( screenName: String, data: SpendCategorizationState.Loaded, spendCategorizationAction: (SpendCategorizationAction) -> Unit, + getEventTracker: () -> SpendAnalysisEventTrackerImpl, ) { Column { SpendCategorizationContentWrapper( @@ -193,7 +208,7 @@ fun SpendCategorizationLoadedUI( mmUIState = MMUIState.Loaded, isCurrentMonth = isCurrentMonth(data.selectedMonth), onTotalSpendsCtaClick = { - DashboardEventTrackerImpl.onDashboardTotalSpendClicked() + getEventTracker().onDashboardTotalSpendClicked() spendCategorizationAction( SpendCategorizationAction.ViewTotalSpends( SelectedMonth(data.selectedMonth, data.selectedYear) @@ -209,6 +224,7 @@ fun SpendCategorizationLoadedUI( spendCategorizationAction(SpendCategorizationAction.SetSpendGoal) }, screenName = screenName, + getEventTracker = getEventTracker, ) SpendCategoriesHeaderTitle( data.categoryHeaderTitlePrefix, @@ -227,6 +243,7 @@ fun SpendCategorizationLoadedUI( ) ) }, + getEventTracker = getEventTracker, ) ViewMoreCta(data.viewMoreCtaText) { spendCategorizationAction( diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/spendCategoriztion/SpendCategory.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/spendCategoriztion/SpendCategory.kt index a9cbda4085..4ff9c3364f 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/spendCategoriztion/SpendCategory.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/spendCategoriztion/SpendCategory.kt @@ -36,7 +36,6 @@ import androidx.compose.ui.unit.sp import com.navi.common.utils.onClickWithDebounce import com.navi.design.font.FontWeightEnum import com.navi.elex.theme.elexColors -import com.navi.moneymanager.common.analytics.DashboardEventTrackerImpl import com.navi.moneymanager.common.analytics.SpendAnalysisEventTrackerImpl import com.navi.moneymanager.common.illustration.model.IllustrationType import com.navi.moneymanager.common.illustration.model.ImageProperties @@ -45,19 +44,20 @@ import com.navi.moneymanager.common.model.MMUIState import com.navi.moneymanager.common.model.SelfTransferCategoryData import com.navi.moneymanager.common.model.SpendCategorizationState import com.navi.moneymanager.common.model.SpendCategoryItemData -import com.navi.moneymanager.common.navigation.utils.MMScreen import com.navi.moneymanager.common.ui.composable.MMCategoryProgressBar import com.navi.moneymanager.common.ui.composable.base.MMText import com.navi.moneymanager.common.utils.fourDpRoundedShape +import com.navi.moneymanager.postonboard.dashboard.model.AddAccountState import com.navi.uitron.utils.setShimmerEffect @Composable fun SpendCategoryList( - screenName: String? = null, + screenName: String, categories: List, mmUIState: MMUIState, applyCategoryHorizontalPadding: Boolean? = true, onCategoryClick: ((SpendCategoryItemData.Loaded) -> Unit)? = null, + getEventTracker: () -> SpendAnalysisEventTrackerImpl, ) { Column(Modifier.padding(top = 16.dp)) { categories.forEachIndexed { index, category -> @@ -71,37 +71,20 @@ fun SpendCategoryList( data = category as SpendCategoryItemData.Loaded, onCategoryClick = onCategoryClick, trackCategoryItemClick = { categoryType -> - when (screenName) { - MMScreen.DASHBOARD.screen -> { - DashboardEventTrackerImpl.onDashboardCategoryClicked( - index + 1, - categoryType, - ) - } - MMScreen.SPEND_ANALYSIS.screen -> { - SpendAnalysisEventTrackerImpl - .onSpendAnalysisScreenCategoryClicked( - index + 1, - categoryType, - ) - } - } + getEventTracker() + .onCategoryClicked( + categoryRank = index + 1, + category = categoryType, + screenName = screenName, + ) }, trackCategoryItemView = { categoryType -> - when (screenName) { - MMScreen.DASHBOARD.screen -> { - DashboardEventTrackerImpl.onDashboardCategoryViewed( - index + 1, - categoryType, - ) - } - MMScreen.SPEND_ANALYSIS.screen -> { - SpendAnalysisEventTrackerImpl.onSpendAnalysisCategoryViewed( - index + 1, - categoryType, - ) - } - } + getEventTracker() + .onCategoryViewed( + categoryRank = index + 1, + category = categoryType, + screenName = screenName, + ) }, ) } @@ -283,7 +266,7 @@ fun SpendCategoryLoadingItem(data: SpendCategoryItemData.Loading) { @Composable fun SpendCategoryEmptyList( - isAddAccountSelected: Boolean, + addAccountState: AddAccountState, data: SpendCategorizationState.Empty, addNewBankAccount: (Boolean) -> Unit, ) { @@ -310,7 +293,7 @@ fun SpendCategoryEmptyList( Spacer(modifier = Modifier.height(24.dp)) AddAccountButton( - isAddAccountSelected = isAddAccountSelected, + addAccountState = addAccountState, data = data, addNewBankAccount = addNewBankAccount, ) @@ -408,96 +391,100 @@ fun SelfTransferCategory( @Composable fun AddAccountButton( - isAddAccountSelected: Boolean, + addAccountState: AddAccountState, data: SpendCategorizationState.Empty, addNewBankAccount: (Boolean) -> Unit, ) { - if (!isAddAccountSelected) { - Row( - modifier = - Modifier.clip(RoundedCornerShape(size = 4.dp)) - .onClickWithDebounce { addNewBankAccount(data.isTotalSyncCompleted) } - .background( - color = elexColors.interactive.background.gray.default, - shape = RoundedCornerShape(size = 4.dp), + when (addAccountState) { + AddAccountState.UnSelected -> { + Row( + modifier = + Modifier.clip(RoundedCornerShape(size = 4.dp)) + .onClickWithDebounce { addNewBankAccount(data.isTotalSyncCompleted) } + .background( + color = elexColors.interactive.background.gray.default, + shape = RoundedCornerShape(size = 4.dp), + ) + .padding(start = 12.dp, end = 16.dp, top = 12.dp, bottom = 12.dp), + horizontalArrangement = Arrangement.spacedBy(6.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + data.ctaIconUrl?.let { + Illustration( + illustrationType = + if (data.isTotalSyncCompleted) + IllustrationType.Image( + it, + properties = + ImageProperties( + colorFilter = + ColorFilter.tint( + elexColors.interactive.icon.gray.standard + ) + ), + ) + else + IllustrationType.Image( + source = it, + properties = + ImageProperties( + colorFilter = + ColorFilter.tint( + elexColors.interactive.icon.primary.subtle + ) + ), + ), + modifier = Modifier.size(16.dp), ) - .padding(start = 12.dp, end = 16.dp, top = 12.dp, bottom = 12.dp), - horizontalArrangement = Arrangement.spacedBy(6.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - data.ctaIconUrl?.let { - Illustration( - illustrationType = - if (data.isTotalSyncCompleted) - IllustrationType.Image( - it, - properties = - ImageProperties( - colorFilter = - ColorFilter.tint( - elexColors.interactive.icon.gray.standard - ) - ), - ) - else - IllustrationType.Image( - source = it, - properties = - ImageProperties( - colorFilter = - ColorFilter.tint( - elexColors.interactive.icon.primary.subtle - ) - ), - ), - modifier = Modifier.size(16.dp), - ) - } - data.addAccountCtaText?.let { - MMText( - text = it, - color = - if (data.isTotalSyncCompleted) elexColors.interactive.text.gray.standard - else elexColors.interactive.text.primary.subtle, - fontSize = 12.sp, - fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD, - textAlign = TextAlign.Center, - lineHeight = 18.sp, - ) - } - } - } else { - Row( - modifier = - Modifier.clip(RoundedCornerShape(size = 4.dp)) - .clickable(enabled = false) {} - .background( - color = elexColors.interactive.background.gray.highlighted, - shape = RoundedCornerShape(size = 4.dp), - ) - .padding(start = 14.dp, end = 16.dp, top = 8.dp, bottom = 8.dp), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - ) { - Box(contentAlignment = Alignment.Center) { + } data.addAccountCtaText?.let { MMText( text = it, + color = + if (data.isTotalSyncCompleted) elexColors.interactive.text.gray.standard + else elexColors.interactive.text.primary.subtle, fontSize = 12.sp, - lineHeight = 18.sp, fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD, - color = elexColors.interactive.text.gray.standard, textAlign = TextAlign.Center, - modifier = Modifier.alpha(0f).padding(start = 16.dp), - ) - } - data.loadingLottieUrl?.let { - Illustration( - illustrationType = IllustrationType.Lottie(it), - modifier = Modifier.height(24.dp).width(36.dp), + lineHeight = 18.sp, ) } } } + AddAccountState.Selected.SpentCategorizationSection -> { + Row( + modifier = + Modifier.clip(RoundedCornerShape(size = 4.dp)) + .clickable(enabled = false) {} + .background( + color = elexColors.interactive.background.gray.highlighted, + shape = RoundedCornerShape(size = 4.dp), + ) + .padding(start = 14.dp, end = 16.dp, top = 8.dp, bottom = 8.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + ) { + Box(contentAlignment = Alignment.Center) { + data.addAccountCtaText?.let { + MMText( + text = it, + fontSize = 12.sp, + lineHeight = 18.sp, + fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD, + color = elexColors.interactive.text.gray.standard, + textAlign = TextAlign.Center, + modifier = Modifier.alpha(0f).padding(start = 16.dp), + ) + } + data.loadingLottieUrl?.let { + Illustration( + illustrationType = IllustrationType.Lottie(it), + modifier = Modifier.height(24.dp).width(36.dp), + ) + } + } + } + } + else -> {} } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/transaction/Transaction.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/transaction/Transaction.kt index fefa85db6b..2134577014 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/transaction/Transaction.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/transaction/Transaction.kt @@ -32,6 +32,7 @@ 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.common.utils.conditional import com.navi.common.utils.onClickWithDebounce import com.navi.design.font.FontWeightEnum import com.navi.elex.theme.elexColors @@ -197,7 +198,9 @@ private fun CategoryPill( shape = RoundedCornerShape(size = 32.dp), ) .clip(RoundedCornerShape(size = 32.dp)) - .onClickWithDebounce { onClick(transaction) } + .conditional(transaction.isCategoryClickable) { + onClickWithDebounce { onClick(transaction) } + } .padding( start = icon?.let { 8.dp } ?: 12.dp, top = 4.dp, diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/transaction/TransactionListSectionUI.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/transaction/TransactionListSectionUI.kt index ad2b8bd267..54489ae7de 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/transaction/TransactionListSectionUI.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/transaction/TransactionListSectionUI.kt @@ -24,7 +24,9 @@ fun TransactionListSectionUI( transactions: List, onTransactionClick: (String) -> Unit, onTransactionCategoryClick: (Transaction) -> Unit, + transactionHistoryEventTracker: () -> TransactionHistoryEventTrackerImpl, ) { + Column( modifier = Modifier.fillMaxWidth().padding(top = 8.dp), horizontalAlignment = Alignment.CenterHorizontally, @@ -34,14 +36,15 @@ fun TransactionListSectionUI( Transaction( transaction = transaction, onClick = { - TransactionHistoryEventTrackerImpl.onTransactionHistoryTransactionClicked( - source = source, - transactionRank = index + 1, - ) + transactionHistoryEventTracker() + .onTransactionHistoryTransactionClicked( + source = source, + transactionRank = index + 1, + ) onTransactionClick(transaction.id) }, onCategoryClick = { - TransactionHistoryEventTrackerImpl + transactionHistoryEventTracker() .onTransactionHistoryTransactionCategoryClicked( source = source, transactionRank = index + 1, diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/upi/UpiTransactionHistorySpendAnalyserWidget.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/upi/UpiTransactionHistorySpendAnalyserWidget.kt new file mode 100644 index 0000000000..1c211a62dc --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/upi/UpiTransactionHistorySpendAnalyserWidget.kt @@ -0,0 +1,333 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.ui.composable.upi + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.LineHeightStyle +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.common.utils.onClickWithDebounce +import com.navi.design.font.FontWeightEnum +import com.navi.design.utils.BackgroundDrawableData +import com.navi.design.utils.DrawableShape +import com.navi.moneymanager.common.analytics.UpiTransactionHistorySpendAnalyserEventTrackerImpl +import com.navi.moneymanager.common.illustration.model.IllustrationSource +import com.navi.moneymanager.common.illustration.model.IllustrationType +import com.navi.moneymanager.common.illustration.model.ImageProperties +import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.CROSS_PURPLE +import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.IMAGE_TRANSPARENT_PLACEHOLDER_SMALL +import com.navi.moneymanager.common.illustration.ui.Illustration +import com.navi.moneymanager.common.model.upi.TopSpendCategoryItemData +import com.navi.moneymanager.common.model.upi.UpiTxnHistorySpendAnalysisData +import com.navi.moneymanager.common.ui.composable.MMCategoryProgressBar +import com.navi.moneymanager.common.ui.composable.base.MMText +import com.navi.moneymanager.common.utils.Constants.SPEND_ANALYSIS_EXP_CROSS_VARIANT +import com.navi.moneymanager.common.utils.fourDpRoundedShape +import com.navi.naviwidgets.extensions.CustomTooltip +import com.navi.naviwidgets.models.response.TooltipProperties +import com.navi.uitron.model.ui.PeakDirection +import com.navi.uitron.model.ui.PeakPosition +import com.navi.uitron.model.ui.ToolTipShapeData + +@Composable +fun UpiTransactionHistorySpendAnalyserWidget( + variant: String, + data: UpiTxnHistorySpendAnalysisData, + onClick: () -> Unit, + onCrossIconClick: () -> Unit, + upiTransactionHistorySpendAnalyserEventTracker: + UpiTransactionHistorySpendAnalyserEventTrackerImpl, +) { + + LaunchedEffect(Unit) { + upiTransactionHistorySpendAnalyserEventTracker + .onUpiTransactionHistorySpendAnalyserWidgetVisible(variant) + } + if (variant == SPEND_ANALYSIS_EXP_CROSS_VARIANT) { + + Box(modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp)) { + Canvas(modifier = Modifier.matchParentSize()) { + drawRoundRect( + color = Color(0xFFE3E5E5), + size = size, + style = Stroke(width = 1.dp.toPx()), + cornerRadius = CornerRadius(4.dp.toPx()), + ) + } + + Box( + contentAlignment = Alignment.Center, + modifier = + Modifier.size(24.dp) + .offset(x = (5).dp, y = (-8).dp) + .background(color = Color(0xFFF9F9FA), shape = CircleShape) + .align(Alignment.TopEnd) + .clip(CircleShape) + .clickable(onClick = onCrossIconClick), + ) { + Illustration( + illustrationType = + IllustrationType.Image( + source = + IllustrationSource.Remote( + url = CROSS_PURPLE, + placeholder = IMAGE_TRANSPARENT_PLACEHOLDER_SMALL, + ) + ), + modifier = Modifier.size(12.dp), + ) + } + + Column(modifier = Modifier.fillMaxWidth()) { + Spacer(modifier = Modifier.height(16.dp)) + UpiSpendAnalyserWidgetUI(data, onClick) + } + } + } else { + CustomTooltip( + properties = + TooltipProperties( + background = + BackgroundDrawableData( + shape = DrawableShape.RECTANGLE.name, + cornerRadius = 4, + strokeWidth = 1, + strokeColor = "#E3E5E5", + peakAspectRatio = 2.1f, + ), + tooltipShapeProperties = + ToolTipShapeData( + peakHeight = 8, + peakDirection = PeakDirection.TOP, + peakPosition = PeakPosition.CENTER, + peakRadius = 4, + bottomPeakRadius = 0, + offsetPercentage = -0.8f, + ), + ), + modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 8.dp).fillMaxWidth(), + ) { + Spacer(modifier = Modifier.height(24.dp)) + + UpiSpendAnalyserWidgetUI(data, onClick) + } + } +} + +@Composable +private fun UpiSpendAnalyserWidgetUI(data: UpiTxnHistorySpendAnalysisData, onClick: () -> Unit) { + Row( + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Spacer(modifier = Modifier.height(1.dp).weight(1f).background(color = Color(0xFFEBEBEB))) + MMText( + text = data.title, + color = Color(0xFF6B6B6B), + fontSize = 12.sp, + letterSpacing = 1.5.sp, + fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD, + textAlign = TextAlign.Center, + lineHeight = 20.sp, + modifier = Modifier.padding(horizontal = 4.dp), + style = + TextStyle( + lineHeightStyle = + LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ) + ), + ) + Spacer(modifier = Modifier.height(1.dp).weight(1f).background(color = Color(0xFFEBEBEB))) + } + + Spacer(modifier = Modifier.height(12.dp)) + + data.spendCategoryItem?.let { TopCategoryItem(data = it) } + ?: run { + MMText( + text = data.subTitle.orEmpty(), + color = Color(0xFF6B6B6B), + fontSize = 14.sp, + fontWeight = FontWeightEnum.NAVI_BODY_REGULAR, + textAlign = TextAlign.Center, + lineHeight = 20.sp, + modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 6.dp), + style = + TextStyle( + lineHeightStyle = + LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ) + ), + ) + } + + Box(Modifier.padding(top = 16.dp, bottom = 16.dp, start = 16.dp, end = 16.dp)) { + Row( + horizontalArrangement = Arrangement.Center, + modifier = + Modifier.fillMaxWidth() + .background(color = Color(0xFFF5F5F5), shape = fourDpRoundedShape) + .clip(RoundedCornerShape(size = 4.dp)) + .onClickWithDebounce { onClick() } + .padding(horizontal = 16.dp, vertical = 12.dp), + ) { + MMText( + text = data.ctaText, + color = Color(0xFF1F002A), + fontSize = 14.sp, + fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD, + textAlign = TextAlign.Center, + lineHeight = 22.sp, + style = + TextStyle( + lineHeightStyle = + LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ) + ), + ) + } + } +} + +@Composable +private fun TopCategoryItem(data: TopSpendCategoryItemData) { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + ) { + Box( + contentAlignment = Alignment.Center, + modifier = + Modifier.size(40.dp) + .background(color = Color(0xFFF5F5F5), shape = CircleShape) + .clip(CircleShape), + ) { + Illustration( + illustrationType = + IllustrationType.Image( + source = data.iconUrl, + properties = + ImageProperties(colorFilter = ColorFilter.tint(Color(0xFF240030))), + ), + modifier = Modifier.size(28.dp), + ) + } + Column(modifier = Modifier.weight(1f).padding(start = 12.dp)) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + MMText( + modifier = Modifier.weight(1f), + text = data.name, + color = Color(0xFF191919), + fontSize = 14.sp, + fontWeight = FontWeightEnum.NAVI_BODY_REGULAR, + lineHeight = 22.sp, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = + TextStyle( + lineHeightStyle = + LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ) + ), + ) + + Spacer(Modifier.width(8.dp)) + + Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { + MMText( + text = data.amount, + color = Color(0xFF191919), + fontSize = 14.sp, + fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD, + lineHeight = 22.sp, + style = + TextStyle( + lineHeightStyle = + LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ) + ), + ) + } + } + + Spacer(Modifier.height(4.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.weight(1f)) { + Box( + modifier = + Modifier.width(80.dp) + .clip(RoundedCornerShape(5.dp)) + .wrapContentHeight() + .background(Color(0xFFE3E5E5)) + ) { + MMCategoryProgressBar( + progress = data.progress, + color = data.progressColor, + totalWidth = 80.dp, + ) + } + } + + Spacer(Modifier.width(8.dp)) + + MMText( + text = data.progressText, + color = Color(0xFF6B6B6B), + fontSize = 12.sp, + fontWeight = FontWeightEnum.NAVI_BODY_REGULAR, + lineHeight = 16.sp, + ) + } + } + } +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/utils/Constants.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/utils/Constants.kt index a08cc5c29b..084d36ae18 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/utils/Constants.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/utils/Constants.kt @@ -14,10 +14,12 @@ object Constants { const val TRANSITION_DURATION_IN_MILLIS = 400 const val DASHBOARD_ONBOARDING_TIMEOUT = 40000L const val MM_DB_NAME = "mmDatabase" + const val UPI_SPEND_DB_NAME = "upiSpendDatabase" const val ACCOUNT_TABLE = "accountTable" const val TRANSACTION_TABLE = "transactionTable" const val SPEND_GOALS_TABLE = "spendGoalsTable" const val DATA_STORE_TABLE = "dataStoreTable" + const val UPI_SPEND_TRANSACTION_TABLE = "upiSpendTransactionTable" const val ERROR_PAGE_CTA_TEXT = "Try again" const val MM_IS_USER_ONBOARDED = "MM_IS_USER_ONBOARDED" const val NO_INTERNET_TEXT = "No internet" @@ -51,11 +53,17 @@ object Constants { const val DASHBOARD_RECENT_TRANSACTIONS = "DASHBOARD_RECENT_TRANSACTIONS_VIEW" const val DAILY_SPEND_BOTTOMSHEET = "DAILY_SPEND_BOTTOMSHEET" const val TRANSACTION_HISTORY = "TRANSACTION_HISTORY_SCREEN" + const val SOURCE = "SOURCE" + const val THANK_YOU_FOR_YOUR_FEEDBACK = "Thank you for your feedback!" // datastore constants const val IS_FIRST_MONTH_SYNC_COMPLETED = "IS_FIRST_MONTH_SYNC_COMPLETED" const val IS_TOTAL_SYNC_COMPLETED = "IS_TOTAL_SYNC_COMPLETED" + const val IS_UPI_SPEND_TOTAL_SYNC_COMPLETED = "IS_UPI_SPEND_TOTAL_SYNC_COMPLETED" + const val UPI_SPEND_CROSS_VARIANT_DISMISS_TIMESTAMP = + "UPI_SPEND_CROSS_VARIANT_DISMISS_TIMESTAMP" + const val NEW_TRANSACTION_COUNT = "NEW_TRANSACTION_COUNT" const val LAST_TRANSACTION_SYNCED_TIMESTAMP = "LAST_TRANSACTION_SYNCED_TIMESTAMP" @@ -63,6 +71,11 @@ object Constants { const val USER_NAME = "USER_NAME" const val INIT = "INIT" const val RUPEE_SYMBOL = "₹" + const val SELECTED_MONTH = "SELECTED_MONTH" + const val LITMUS_EXPERIMENT_TXN_HISTORY_SPEND_ANALYSIS = "upi_spend_entry_point" + const val SPEND_ANALYSIS_EXP_CROSS_VARIANT = "test-with-dismiss" + const val CONTROL = "Control" + const val JOURNEY_SOURCE = "journey_source" } object DbCacheConstants { diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/utils/Utils.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/utils/Utils.kt index 9e50533998..f348c50f57 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/utils/Utils.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/utils/Utils.kt @@ -50,6 +50,7 @@ import com.navi.base.model.LineItem import com.navi.base.utils.BaseUtils.getDayMonthAndYearFromTimestamp import com.navi.base.utils.COMMA import com.navi.base.utils.EMPTY +import com.navi.base.utils.NaviDateFormatter.DATE_TIME_FORMAT_DATE_MONTH_NAME_YEAR_AT_TIME import com.navi.base.utils.isNotNullAndNotEmpty import com.navi.common.constants.HELP_CTA_TEXT import com.navi.common.extensions.removeSpaces @@ -58,14 +59,17 @@ import com.navi.common.network.models.ErrorMessage import com.navi.common.utils.CommonUtils.formattedCurrency import com.navi.common.utils.Constants.COMMA_CHAR import com.navi.common.utils.onClickWithDebounce +import com.navi.elex.theme.ElexTheme import com.navi.elex.theme.elexColors import com.navi.moneymanager.R import com.navi.moneymanager.common.dataprovider.utils.DataProviderConstants.EE_DD_MMM_YYYY import com.navi.moneymanager.common.dataprovider.utils.DataProviderConstants.MMM import com.navi.moneymanager.common.dataprovider.utils.DataProviderConstants.YYYY -import com.navi.moneymanager.common.model.TotalSpends +import com.navi.moneymanager.common.model.SpendGoalState +import com.navi.moneymanager.common.model.sectionHeader.MMJourneySource import com.navi.moneymanager.common.navigation.navigator.MMDeeplinkNavigator.MONEY_MANAGER_ACTIVITY import com.navi.moneymanager.common.network.model.FinarkeinDataResponse +import com.navi.moneymanager.common.ui.theme.MMTheme import com.navi.moneymanager.common.ui.theme.color.MMColor import com.navi.moneymanager.common.utils.Constants.PRODUCT_HELP_PAGE import com.navi.moneymanager.common.utils.Constants.PRODUCT_HELP_SCREEN_NAME @@ -87,9 +91,9 @@ fun checkFinarkeinDataValidity(finarkeinDataResponse: FinarkeinDataResponse?): B finarkeinDataResponse?.vendorAuthToken.isNotNullAndNotEmpty() } -fun formatDateFromTimestamp(timestampMillis: Long): String { +fun formatDateFromTimestamp(timestampMillis: Long, pattern: String = EE_DD_MMM_YYYY): String { val date = Date(timestampMillis) - val formatter = SimpleDateFormat(EE_DD_MMM_YYYY, Locale("en", "IN")) + val formatter = SimpleDateFormat(pattern, Locale("en", "IN")) return formatter.format(date) } @@ -131,16 +135,6 @@ fun isCurrentMonth(month: Int): Boolean { return currentDateComponents.second == month } -fun TotalSpendsEmptyToLoaded(data: TotalSpends.Empty) = - TotalSpends.Loaded( - iconUrl = data.iconUrl, - title = data.title, - subtitle = data.subtitle, - spendGoal = data.spendGoal, - progress = data.progress, - actionIcon = data.actionIcon, - ) - @Composable fun ShowCustomToast( context: Context, @@ -160,7 +154,7 @@ fun ShowCustomToast( setViewTreeLifecycleOwner(lifecycleOwner) setViewTreeViewModelStoreOwner(viewModelStoreOwner) setViewTreeSavedStateRegistryOwner(savedStateRegistryOwner) - setContent { content() } + setContent { ElexTheme(isDarkThemeEnabled = MMTheme.isInDarkMode) { content() } } } } LaunchedEffect(toast) { @@ -443,9 +437,27 @@ fun formattedAmountToValue(amount: String): String { return amount.removeSpaces().replace(COMMA, EMPTY).replace(RUPEE_SYMBOL, EMPTY) } +fun getSpendGoalState( + isSpendGoalEnabled: Boolean, + spendGoal: Double? = null, + totalSpends: Double = 0.0, +): SpendGoalState { + return when (isSpendGoalEnabled) { + true -> SpendGoalState.Loaded(spendGoal, getSpendGoalProgress(spendGoal, totalSpends)) + false -> SpendGoalState.None + } +} + fun getSpendGoalProgress(spendGoal: Double?, totalAmount: Double): Double? { if (spendGoal == 0.0) return 0.0 val progress = spendGoal?.let { totalAmount.div(it) } progress?.coerceIn(0.0, 1.0) return progress } + +fun getDateFormatPattern(journeySource: String): String { + return when (journeySource) { + MMJourneySource.NAVI_UPI.name -> DATE_TIME_FORMAT_DATE_MONTH_NAME_YEAR_AT_TIME + else -> EE_DD_MMM_YYYY + } +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/entry/ui/activity/MMActivity.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/entry/ui/activity/MMActivity.kt index af26e13f3f..fc1fa8aa0e 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/entry/ui/activity/MMActivity.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/entry/ui/activity/MMActivity.kt @@ -32,10 +32,14 @@ import com.navi.elex.atoms.ElexBottomSheet import com.navi.moneymanager.NavGraphs import com.navi.moneymanager.R import com.navi.moneymanager.common.analytics.FinarkeinEventTrackerImpl +import com.navi.moneymanager.common.analytics.GenericErrorBottomSheetEventTrackerImpl +import com.navi.moneymanager.common.model.SelectedMonth import com.navi.moneymanager.common.navigation.navigator.MMDeeplinkNavigator.MONEY_MANAGER_ACTIVITY import com.navi.moneymanager.common.navigation.utils.MMScreen import com.navi.moneymanager.common.ui.base.MMBaseActivity import com.navi.moneymanager.common.ui.theme.MoneyManagerTheme +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE +import com.navi.moneymanager.common.utils.Constants.SELECTED_MONTH import com.navi.moneymanager.destinations.DashboardScreenDestination import com.navi.moneymanager.destinations.LauncherScreenDestination import com.navi.moneymanager.entry.handler.MMActivityEffectHandler @@ -62,6 +66,18 @@ class MMActivity : MMBaseActivity(), Auth { @Inject lateinit var effectHandler: MMActivityEffectHandler + private val finarkeinEventTracker by lazy { + FinarkeinEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmSharedVM.mmConfigResponseHelper.journeySource) + ) + } + + private val genericErrorBottomSheetEventTracker by lazy { + GenericErrorBottomSheetEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmSharedVM.mmConfigResponseHelper.journeySource) + ) + } + private var finarkeinAccessToken = EMPTY private val isRootedDevice = CommonRootDeviceUtil.instance.rootedDeviceUsingFirebase() || @@ -71,7 +87,7 @@ class MMActivity : MMBaseActivity(), Auth { registerForActivityResult(OpenFinarkeinAnubhav()) { anubhavResult -> when (anubhavResult) { is AnubhavSuccess -> { - FinarkeinEventTrackerImpl.finarkeinSuccess(anubhavResult) + finarkeinEventTracker.finarkeinSuccess(anubhavResult) mmNavigator.navigateTo( activity = this, ctaData = CtaData(url = MMScreen.ACCOUNT_LINKING_SUCCESS.screen), @@ -97,8 +113,10 @@ class MMActivity : MMBaseActivity(), Auth { handleRootedDeviceError() return } + mmSharedVM.setJourneySource(intent.extras) + val selectedMonth = intent.extras?.getParcelable(SELECTED_MONTH) ?: SelectedMonth() val isUserOnBoarded = mmSharedVM.getUserOnboardingStatus() - initComposableContent(isUserOnBoarded) + initComposableContent(isUserOnBoarded, selectedMonth) } override fun onNewIntent(intent: Intent) { @@ -144,6 +162,7 @@ class MMActivity : MMBaseActivity(), Auth { ), onDismiss = { finish() }, screenName = MMScreen.LAUNCHER.screen, + getEventTracker = { genericErrorBottomSheetEventTracker }, ) } } @@ -151,7 +170,7 @@ class MMActivity : MMBaseActivity(), Auth { } } - private fun initComposableContent(isUserOnBoarded: Boolean) { + private fun initComposableContent(isUserOnBoarded: Boolean, selectedMonth: SelectedMonth) { setContent { navController = rememberNavController() val darkModeEnabled = @@ -185,7 +204,10 @@ class MMActivity : MMBaseActivity(), Auth { navGraph = NavGraphs.root, engine = naviHostEngine(), navController = navController, - dependenciesContainerBuilder = { dependency(this@MMActivity) }, + dependenciesContainerBuilder = { + dependency(this@MMActivity) + dependency(selectedMonth) + }, ) } } @@ -197,7 +219,7 @@ class MMActivity : MMBaseActivity(), Auth { val inputs = AnubhavConfiguration.withWebViewUrl(redirectUrl).requestId(requestId).auth(this).build() Finarkein.setEventListener { event -> - FinarkeinEventTrackerImpl.finarkeinJourneyEvent(event) + finarkeinEventTracker.finarkeinJourneyEvent(event) mmSharedVM.setEffect { MMActivityUIEffect.FinarkeinSdkEvent(event) } } anubhav?.launch(inputs) diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/entry/viewmodel/MMSharedViewModel.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/entry/viewmodel/MMSharedViewModel.kt index 1cb1816e3f..46432a9834 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/entry/viewmodel/MMSharedViewModel.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/entry/viewmodel/MMSharedViewModel.kt @@ -7,6 +7,7 @@ package com.navi.moneymanager.entry.viewmodel +import android.os.Bundle import androidx.lifecycle.viewModelScope import com.navi.base.model.CtaData import com.navi.base.sharedpref.PreferenceManager @@ -15,10 +16,14 @@ import com.navi.common.navigation.NavigationAction import com.navi.common.utils.FirebaseEventFacade import com.navi.common.viewmodel.BaseVM import com.navi.moneymanager.common.analytics.FinarkeinEventTrackerImpl +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper import com.navi.moneymanager.common.manager.MMLibManager +import com.navi.moneymanager.common.model.sectionHeader.MMJourneySource import com.navi.moneymanager.common.navigation.navigator.MMNavigator import com.navi.moneymanager.common.navigation.utils.MMScreen +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE import com.navi.moneymanager.common.utils.Constants.MM_IS_USER_ONBOARDED +import com.navi.moneymanager.common.utils.Constants.SOURCE import com.navi.moneymanager.entry.model.MMActivityUIEffect import com.navi.moneymanager.entry.ui.activity.MMActivity import dagger.hilt.android.lifecycle.HiltViewModel @@ -35,11 +40,20 @@ import kotlinx.coroutines.flow.receiveAsFlow @HiltViewModel class MMSharedViewModel @Inject -constructor(val firebaseEventFacade: FirebaseEventFacade, private val mmLibManager: MMLibManager) : - BaseVM() { +constructor( + val firebaseEventFacade: FirebaseEventFacade, + private val mmLibManager: MMLibManager, + val mmConfigResponseHelper: MMConfigResponseHelper, +) : BaseVM() { var valuePropScreenDefinition: AlchemistScreenDefinition? = null private set + private val finarkeinEventTracker by lazy { + FinarkeinEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + private val _showFinarkeinErrorBottomSheet = MutableSharedFlow(replay = 1) val showFinarkeinErrorBottomSheet = _showFinarkeinErrorBottomSheet.asSharedFlow() @@ -50,12 +64,17 @@ constructor(val firebaseEventFacade: FirebaseEventFacade, private val mmLibManag viewModelScope.safeLaunch(dispatcher ?: Dispatchers.IO) { _effects.trySend(effect()) } } + fun setJourneySource(bundle: Bundle?) { + bundle?.apply { getString(SOURCE)?.let { mmConfigResponseHelper.setJourneySource(it) } } + } + fun setValuePropScreenDefinition(newDefinition: AlchemistScreenDefinition) { valuePropScreenDefinition = newDefinition } fun getUserOnboardingStatus(): Boolean { - return PreferenceManager.getBooleanPreference(key = MM_IS_USER_ONBOARDED) + return PreferenceManager.getBooleanPreference(key = MM_IS_USER_ONBOARDED) || + mmConfigResponseHelper.journeySource == MMJourneySource.NAVI_UPI.name } fun handleFinarkeinError(anubhavExit: AnubhavExit) { @@ -66,9 +85,9 @@ constructor(val firebaseEventFacade: FirebaseEventFacade, private val mmLibManag AnubhavConstants.ERROR_CODE_CONSENT_REJECTED, ) if (anubhavExit.error?.errorCode in ignoredErrorCodes) { - FinarkeinEventTrackerImpl.finarkeinUserExit(anubhavExit) + finarkeinEventTracker.finarkeinUserExit(anubhavExit) } else { - FinarkeinEventTrackerImpl.finarkeinError(anubhavExit) + finarkeinEventTracker.finarkeinError(anubhavExit) updateFinarkeinErrorBottomSheetState(show = true) } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/model/CategoryDetailsScreenData.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/model/CategoryDetailsScreenData.kt index 4f7fefd524..6301d08910 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/model/CategoryDetailsScreenData.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/model/CategoryDetailsScreenData.kt @@ -11,9 +11,11 @@ import com.navi.moneymanager.common.illustration.model.IllustrationSource import com.navi.moneymanager.common.model.BarGraphData import com.navi.moneymanager.common.model.Transaction import com.navi.moneymanager.common.model.ZeroTransactionData +import com.navi.moneymanager.postonboard.dashboard.model.NavBarData import com.navi.moneymanager.postonboard.spendanalysis.model.TotalSpendSectionData data class CategoryDetailsScreenData( + val topNavBar: NavBarData = NavBarData(), val totalSpendSection: TotalSpendSectionData? = null, val barGraphData: BarGraphData? = null, val sortOption: SortOption = SortOption.RECENT_FIRST, diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/repo/CategoryDetailsRepository.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/repo/CategoryDetailsRepository.kt index ea9da09f03..4fd15d26b1 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/repo/CategoryDetailsRepository.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/repo/CategoryDetailsRepository.kt @@ -1,22 +1,41 @@ /* * - * * Copyright © 2024 by Navi Technologies Limited + * * Copyright © 2024-2025 by Navi Technologies Limited * * All rights reserved. Strictly confidential * */ package com.navi.moneymanager.postonboard.categorydetails.repo +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper import com.navi.moneymanager.common.dataprovider.domain.CategoryDetailsLocalDataProvider +import com.navi.moneymanager.common.dataprovider.domain.UpiSpendCategoryDetailsLocalDataProvider +import com.navi.moneymanager.common.model.Transaction +import com.navi.moneymanager.common.model.sectionHeader.MMJourneySource +import com.navi.moneymanager.postonboard.categorydetails.model.CategoryDetailsScreenData +import com.navi.moneymanager.postonboard.categorydetails.model.CategorySelectionBottomSheetData import com.navi.moneymanager.postonboard.categorydetails.model.SortOption import javax.inject.Inject +import kotlinx.coroutines.flow.Flow class CategoryDetailsRepository @Inject -constructor(private val categoryDetailsLocalDataProvider: CategoryDetailsLocalDataProvider) { +constructor( + private val categoryDetailsLocalDataProvider: CategoryDetailsLocalDataProvider, + private val upiSpendCategoryDetailsLocalDataProvider: UpiSpendCategoryDetailsLocalDataProvider, + private val mmConfigResponseHelper: MMConfigResponseHelper, +) { - suspend fun getSortedTransactions(sort: SortOption) = - categoryDetailsLocalDataProvider.getSortedTransactions(sortOption = sort) + suspend fun getSortedTransactions(sort: SortOption): List { + return when (mmConfigResponseHelper.journeySource) { + MMJourneySource.NAVI_UPI.name -> { + upiSpendCategoryDetailsLocalDataProvider.getSortedTransactions(sortOption = sort) + } + else -> { + categoryDetailsLocalDataProvider.getSortedTransactions(sortOption = sort) + } + } + } suspend fun getCategoryDetailsScreenData( month: Int?, @@ -24,14 +43,27 @@ constructor(private val categoryDetailsLocalDataProvider: CategoryDetailsLocalDa sort: SortOption, selectedBankReferenceIds: Set, category: String, - ) = - categoryDetailsLocalDataProvider.getCategoryDetailsScreenData( - month, - year, - sort, - selectedBankReferenceIds, - category, - ) + ): Flow { + return when (mmConfigResponseHelper.journeySource) { + MMJourneySource.NAVI_UPI.name -> { + upiSpendCategoryDetailsLocalDataProvider.getCategoryDetailsScreenData( + month, + year, + sort, + category, + ) + } + else -> { + categoryDetailsLocalDataProvider.getCategoryDetailsScreenData( + month, + year, + sort, + selectedBankReferenceIds, + category, + ) + } + } + } suspend fun getMonthSelectionBottomSheetData(displayMonthText: String) = categoryDetailsLocalDataProvider.getMonthSelectionBottomSheetData(displayMonthText) @@ -42,8 +74,22 @@ constructor(private val categoryDetailsLocalDataProvider: CategoryDetailsLocalDa suspend fun getPastMonthDataLoadingBottomSheetData() = categoryDetailsLocalDataProvider.getPastMonthDataLoadingBottomSheetData() - suspend fun getCategorySelectionBottomSheetData(selectedCategory: String) = - categoryDetailsLocalDataProvider.getCategorySelectionBottomSheetData(selectedCategory) + suspend fun getCategorySelectionBottomSheetData( + selectedCategory: String + ): CategorySelectionBottomSheetData { + return when (mmConfigResponseHelper.journeySource) { + MMJourneySource.NAVI_UPI.name -> { + upiSpendCategoryDetailsLocalDataProvider.getCategorySelectionBottomSheetData( + selectedCategory + ) + } + else -> { + categoryDetailsLocalDataProvider.getCategorySelectionBottomSheetData( + selectedCategory + ) + } + } + } fun getUncategorizedInfoBottomSheetData() = categoryDetailsLocalDataProvider.getUncategorizedInfoBottomSheetData() diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/ui/CategoryDetailsScaffoldRenderer.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/ui/CategoryDetailsScaffoldRenderer.kt index c34dddf87f..c23a40f78a 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/ui/CategoryDetailsScaffoldRenderer.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/ui/CategoryDetailsScaffoldRenderer.kt @@ -45,12 +45,10 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.navi.base.utils.EMPTY -import com.navi.common.constants.HELP_CTA_TEXT import com.navi.common.utils.onClickWithDebounce import com.navi.design.font.FontWeightEnum import com.navi.elex.theme.elexColors import com.navi.moneymanager.R -import com.navi.moneymanager.common.analytics.CategoryDetailsEventTrackerImpl import com.navi.moneymanager.common.composable.bankselectionbottomsheet.ZeroTransactionView import com.navi.moneymanager.common.illustration.model.IllustrationSource import com.navi.moneymanager.common.illustration.model.IllustrationType @@ -119,11 +117,16 @@ fun CategoryDetailsScaffoldRenderer( }, titleColor = elexColors.interactive.icon.primary.standard, navigationIcon = com.navi.common.R.drawable.ic_arrow_left_black_v2, - actionIconText = if (!showScrolledStateTopSection) HELP_CTA_TEXT else null, + actionIconText = + if (!showScrolledStateTopSection) + categoryDetailsScreenUiState().screenData?.topNavBar?.actionLabel + else null, onActionClick = if (!showScrolledStateTopSection) { { - CategoryDetailsEventTrackerImpl.onCategoryDetailsHelpClicked() + getViewModel() + .categoryDetailsEventTracker + .onCategoryDetailsHelpClicked() getViewModel().setEffect { CategoryDetailsScreenUiEffect.Navigation.Help } @@ -163,6 +166,7 @@ fun CategoryDetailsScaffoldRenderer( barGraphData = barGraphData, onAverageInfoClick = onAverageInfoClick, onBarGraphElementClicked = onBarGraphElementClicked, + getEventTracker = { getViewModel().categoryDetailsEventTracker }, ) } } @@ -218,9 +222,11 @@ fun CategoryDetailsScaffoldRenderer( Transaction( transaction = transaction, onClick = { transactionId -> - CategoryDetailsEventTrackerImpl.onCategoryDetailsTransactionClicked( - transactionRank = index + 1 - ) + getViewModel() + .categoryDetailsEventTracker + .onCategoryDetailsTransactionClicked( + transactionRank = index + 1 + ) getViewModel().setEffect { CategoryDetailsScreenUiEffect.Navigation.TransactionDetails( transactionId = transactionId @@ -228,13 +234,16 @@ fun CategoryDetailsScaffoldRenderer( } }, onCategoryClick = { txn -> - CategoryDetailsEventTrackerImpl - .onCategoryDetailsTransactionCategoryClicked( - transactionRank = index + 1, - category = txn.categoryId, - ) - getViewModel().setEffect { - CategoryDetailsScreenUiEffect.OpenCategoryBottomSheet(txn) + if (txn.isCategoryClickable) { + getViewModel() + .categoryDetailsEventTracker + .onCategoryDetailsTransactionCategoryClicked( + transactionRank = index + 1, + category = txn.categoryId, + ) + getViewModel().setEffect { + CategoryDetailsScreenUiEffect.OpenCategoryBottomSheet(txn) + } } }, ) diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/ui/CategoryDetailsScreen.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/ui/CategoryDetailsScreen.kt index 7799418eb0..448533ba8e 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/ui/CategoryDetailsScreen.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/ui/CategoryDetailsScreen.kt @@ -84,13 +84,13 @@ fun CategoryDetailsScreen( screenData.value?.month ?: getDayMonthAndYearFromTimestamp(System.currentTimeMillis()).second ) - CategoryDetailsEventTrackerImpl.onCategoryDetailsScreenLanded( + viewModel.categoryDetailsEventTracker.onCategoryDetailsScreenLanded( category = screenData.value?.selectedCategory.orEmpty(), monthIndex = selectedMonthIndex, numberOfBanks = screenData.value?.selectedBanks?.size.orZero(), ) }, - onScreenExit = { CategoryDetailsEventTrackerImpl.onCategoryDetailsScreenExit() }, + onScreenExit = { viewModel.categoryDetailsEventTracker.onCategoryDetailsScreenExit() }, ) ScreenInit(screenName = MMScreen.CATEGORY_DETAILS.screen, activity = activity) @@ -102,12 +102,14 @@ fun CategoryDetailsScreen( is CategoryDetailsScreenUiEffect.Navigation.Back -> { navigateToPreviousScreen(activity) } + is CategoryDetailsScreenUiEffect.Navigation.NavigateToCta -> { activity.mmNavigator.navigateTo( activity = activity, ctaData = effect.ctaData, ) } + CategoryDetailsScreenUiEffect.Navigation.Help -> { viewModel.sendEvent( CategoryDetailsScreenUiEvent.ShowBottomSheet( @@ -115,6 +117,7 @@ fun CategoryDetailsScreen( ) ) } + is CategoryDetailsScreenUiEffect.Navigation.TransactionDetails -> { activity.mmNavigator.navigateTo( activity = activity, @@ -123,15 +126,18 @@ fun CategoryDetailsScreen( Bundle().apply { putString(TRANSACTION_ID, effect.transactionId) }, ) } + is CategoryDetailsScreenUiEffect.OpenCategoryBottomSheet -> { viewModel.sendEvent( CategoryDetailsScreenUiEvent.UpdateTransactionData(effect.transaction) ) showBottomSheet.value = true } + is CategoryDetailsScreenUiEffect.SortTransactions -> { viewModel.sortTransactions(sortOption = effect.sortOption) } + CategoryDetailsScreenUiEffect.FetchConsentUrl -> { viewModel.fetchConsentUrl() } @@ -155,27 +161,27 @@ fun CategoryDetailsScreen( categoryDetailsScreenUiState = { state }, getViewModel = { viewModel }, onMonthChangeClick = { - CategoryDetailsEventTrackerImpl.onCategoryDetailsMonthSelectionRequested() + viewModel.categoryDetailsEventTracker.onCategoryDetailsMonthSelectionRequested() viewModel.handleMonthSelectBottomSheet(it) }, onBankSelectionRequest = { - CategoryDetailsEventTrackerImpl.onCategoryDetailsBankSelectionRequested() + viewModel.categoryDetailsEventTracker.onCategoryDetailsBankSelectionRequested() viewModel.handleBankSelectBottomSheet(it) }, onAverageInfoClick = { averageValue -> - CategoryDetailsEventTrackerImpl.onCategoryDetailsGraphAverageInfoClicked() + viewModel.categoryDetailsEventTracker.onCategoryDetailsGraphAverageInfoClicked() viewModel.handleAverageInfoBottomSheet(averageValue) }, onCategoryClick = { - CategoryDetailsEventTrackerImpl.onCategoryDetailsCategorySelectionRequested() + viewModel.categoryDetailsEventTracker.onCategoryDetailsCategorySelectionRequested() viewModel.handleCategorySelectionBottomSheet(it) }, onInfoButtonClick = { - CategoryDetailsEventTrackerImpl.onCategoryDetailsUncategorizedInfoClicked() + viewModel.categoryDetailsEventTracker.onCategoryDetailsUncategorizedInfoClicked() viewModel.handleUncategorizedInfoBottomSheet() }, onSortTransactionsRequest = { - CategoryDetailsEventTrackerImpl.onCategoryDetailsSortTransactionsRequested() + viewModel.categoryDetailsEventTracker.onCategoryDetailsSortTransactionsRequested() viewModel.handleSortTransactionsBottomSheet() }, onBarGraphElementClicked = { @@ -194,6 +200,7 @@ fun CategoryDetailsScreen( onEvent = { viewModel.sendEvent(it) }, screenData = screenData, onEffect = { viewModel.setEffect { it } }, + getEventTracker = { viewModel.categoryDetailsEventTracker }, ) } @@ -239,6 +246,7 @@ private fun CategoryDetailsBottomSheetContentHandler( dataStoreInfoProvider: DataStoreInfoProvider, screenData: MutableState, onEffect: (CategoryDetailsScreenUiEffect) -> Unit, + getEventTracker: () -> CategoryDetailsEventTrackerImpl, ) { if (bottomSheet.sheetData == null) return val onDismissAction = bottomSheet.createDismissAction(onEvent) @@ -248,15 +256,17 @@ private fun CategoryDetailsBottomSheetContentHandler( bottomSheetData = bottomSheet.data, onBackIconClicked = { onDismissAction() }, onBankSelectionApplied = { selectedBankReferenceIds -> - CategoryDetailsEventTrackerImpl.onCategoryDetailsBankSelectionApplied( - numberOfSelectedBankAccounts = selectedBankReferenceIds.size - ) + getEventTracker() + .onCategoryDetailsBankSelectionApplied( + numberOfSelectedBankAccounts = selectedBankReferenceIds.size + ) screenData.value = screenData.value?.copy(selectedBanks = selectedBankReferenceIds) onDismissAction() }, ) } + is CategoryDetailsScreenBottomSheets.MonthSelection -> { MonthSelectionBottomSheetUI( screenName = MMScreen.CATEGORY_DETAILS.screen, @@ -270,19 +280,22 @@ private fun CategoryDetailsBottomSheetContentHandler( year = month.second, ) }, + getEventTracker = getEventTracker, ) } + is CategoryDetailsScreenBottomSheets.AverageInfo -> { AverageInfoBottomSheetContent( screenName = MMScreen.CATEGORY_DETAILS.screen, averageValue = bottomSheet.averageValue.orEmpty(), onDismiss = { - CategoryDetailsEventTrackerImpl - .onCategoryDetailsGraphAverageInfoBottomSheetAcknowledged() + getEventTracker().onCategoryDetailsGraphAverageInfoBottomSheetAcknowledged() onDismissAction() }, + getEventTracker = getEventTracker, ) } + is CategoryDetailsScreenBottomSheets.PastMonthDataLoading -> { LaunchedEffect(Unit) { dataStoreInfoProvider.getBooleanData(key = IS_TOTAL_SYNC_COMPLETED).collect { @@ -296,15 +309,14 @@ private fun CategoryDetailsBottomSheetContentHandler( data = bottomSheet.data!!, onDismiss = { onDismissAction() }, trackBottomSheetAppears = { - CategoryDetailsEventTrackerImpl - .onCategoryDetailsPastMonthDataLoadingBottomSheetAppeared() + getEventTracker().onCategoryDetailsPastMonthDataLoadingBottomSheetAppeared() }, trackBottomSheetDisappears = { - CategoryDetailsEventTrackerImpl - .onCategoryDetailsPastMonthDataLoadingBottomSheetDisappeared() + getEventTracker().onCategoryDetailsPastMonthDataLoadingBottomSheetDisappeared() }, ) } + is CategoryDetailsScreenBottomSheets.CategorySelection -> { CategorySelectionBottomSheetContent( data = bottomSheet.data!!, @@ -312,29 +324,32 @@ private fun CategoryDetailsBottomSheetContentHandler( onCategorySelection = { screenData.value = screenData.value?.copy(selectedCategory = it) }, + getEventTracker = getEventTracker, ) } + is CategoryDetailsScreenBottomSheets.UncategorizedInfo -> { UncategorizedInfoBottomSheetContent( data = bottomSheet.data!!, onDismiss = { onDismissAction() }, + getEventTracker = getEventTracker, ) } + CategoryDetailsScreenBottomSheets.SortTransactions -> { state.screenData?.sortOption?.let { currentSortOption -> SortTransactionsBottomSheetContent( onDismiss = { onDismissAction() }, currentSortOption = currentSortOption, onSortApplied = { sortOption -> - CategoryDetailsEventTrackerImpl.onCategoryDetailsSortTransactionsApplied( - sortOption.name - ) + getEventTracker().onCategoryDetailsSortTransactionsApplied(sortOption.name) onEffect(CategoryDetailsScreenUiEffect.SortTransactions(sortOption)) onDismissAction() }, ) } } + is CategoryDetailsScreenBottomSheets.HelpBottomSheet -> { HelpBottomSheetContent( screenName = MMScreen.CATEGORY_DETAILS.screen, @@ -354,8 +369,10 @@ private fun CategoryDetailsBottomSheetContentHandler( ) }, onDismiss = onDismissAction, + getEventTracker = getEventTracker, ) } + CategoryDetailsScreenBottomSheets.NoBottomSheet -> {} } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/ui/CategorySelectionBottomSheetContent.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/ui/CategorySelectionBottomSheetContent.kt index 974823b639..aeea74f214 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/ui/CategorySelectionBottomSheetContent.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/ui/CategorySelectionBottomSheetContent.kt @@ -53,6 +53,7 @@ fun CategorySelectionBottomSheetContent( data: CategorySelectionBottomSheetData, onDismiss: () -> Unit, onCategorySelection: (String) -> Unit, + getEventTracker: () -> CategoryDetailsEventTrackerImpl, ) { var currentSelection by remember { mutableStateOf(data.selectedCategory) } var currentlySelectedCategoryIndex by remember { @@ -126,6 +127,7 @@ fun CategorySelectionBottomSheetContent( currentSelection, onCategorySelection, onDismiss, + getEventTracker = getEventTracker, ) }, startIcon = @@ -162,11 +164,13 @@ private fun onItemClick( currentSelection: String, onCategorySelection: (String) -> Unit, onDismiss: () -> Unit, + getEventTracker: () -> CategoryDetailsEventTrackerImpl, ) { - CategoryDetailsEventTrackerImpl.onCategoryDetailsCategorySelectionApplied( - currentlySelectedCategoryIndex + 1, - currentSelection, - ) + getEventTracker() + .onCategoryDetailsCategorySelectionApplied( + currentlySelectedCategoryIndex + 1, + currentSelection, + ) onCategorySelection(currentSelection) onDismiss() } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/ui/UncategorizedInfoBottomSheetContent.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/ui/UncategorizedInfoBottomSheetContent.kt index 44a09aa5ae..8f6f729e0b 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/ui/UncategorizedInfoBottomSheetContent.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/ui/UncategorizedInfoBottomSheetContent.kt @@ -36,6 +36,7 @@ import com.navi.moneymanager.postonboard.categorydetails.model.UncategorizedInfo fun UncategorizedInfoBottomSheetContent( data: UncategorizedInfoBottomSheetData, onDismiss: () -> Unit, + getEventTracker: () -> CategoryDetailsEventTrackerImpl, ) { Column( modifier = @@ -75,8 +76,7 @@ fun UncategorizedInfoBottomSheetContent( fontWeight = ElexFontWeightEnum.NAVI_BODY_DEMI_BOLD, fontSize = 14.sp, onClick = { - CategoryDetailsEventTrackerImpl - .onCategoryDetailsUncategorizedInfoBottomSheetAcknowledged() + getEventTracker().onCategoryDetailsUncategorizedInfoBottomSheetAcknowledged() onDismiss() }, colors = diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/viewmodel/CategoryDetailsVM.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/viewmodel/CategoryDetailsVM.kt index bc8e64b098..7e1d938604 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/viewmodel/CategoryDetailsVM.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/categorydetails/viewmodel/CategoryDetailsVM.kt @@ -10,13 +10,17 @@ package com.navi.moneymanager.postonboard.categorydetails.viewmodel import androidx.lifecycle.viewModelScope import com.navi.base.utils.orZero import com.navi.moneymanager.base.viewmodel.MMBaseViewModel +import com.navi.moneymanager.common.analytics.CategoryDetailsEventTrackerImpl import com.navi.moneymanager.common.analytics.DataProviderEventTrackerImpl +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper import com.navi.moneymanager.common.dataprovider.data.datastore.DataStoreInfoProvider import com.navi.moneymanager.common.model.CategoryDetailsScreenInputParams import com.navi.moneymanager.common.model.bottomSheet.CategoryDetailsScreenBottomSheets +import com.navi.moneymanager.common.model.sectionHeader.MMJourneySource import com.navi.moneymanager.common.navigation.utils.MMScreen import com.navi.moneymanager.common.network.di.RoomDataStoreInfoProvider import com.navi.moneymanager.common.utils.Constants.IS_TOTAL_SYNC_COMPLETED +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE import com.navi.moneymanager.postonboard.categorydetails.model.CategoryDetailsScreenData import com.navi.moneymanager.postonboard.categorydetails.model.CategoryDetailsScreenUiEffect import com.navi.moneymanager.postonboard.categorydetails.model.CategoryDetailsScreenUiEvent @@ -49,6 +53,7 @@ constructor( @RoomDataStoreInfoProvider val dbDataStoreProvider: DataStoreInfoProvider, private val repository: CategoryDetailsRepository, private val manageConsentUseCase: ManageConsentUseCase, + private val mmConfigResponseHelper: MMConfigResponseHelper, ) : MMBaseViewModel< CategoryDetailsScreenUiState, @@ -61,13 +66,25 @@ constructor( private val screenParams = MutableStateFlow(null) + private val dataProviderEventTracker by lazy { + DataProviderEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + + val categoryDetailsEventTracker by lazy { + CategoryDetailsEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + @OptIn(ExperimentalCoroutinesApi::class) private val categoryDetailsScreenData: StateFlow = screenParams .filterNotNull() .distinctUntilChanged() .flatMapLatest { params -> - DataProviderEventTrackerImpl.categoryDetailsVmCollect( + dataProviderEventTracker.categoryDetailsVmCollect( month = params.month.toString(), year = params.year.toString(), selectedBanksListSize = params.selectedBankReferenceIds?.size.orZero(), @@ -109,7 +126,7 @@ constructor( selectedCategory: String?, ) { viewModelScope.safeLaunch(Dispatchers.IO) { - DataProviderEventTrackerImpl.categoryDetailsVmParamsUpdate( + dataProviderEventTracker.categoryDetailsVmParamsUpdate( month = month.toString(), year = year.toString(), selectedBanksListSize = selectedBankReferenceIds?.size.orZero(), @@ -130,7 +147,10 @@ constructor( viewModelScope.safeLaunch(Dispatchers.IO) { val isTotalSyncCompleted = dbDataStoreProvider.getBooleanData(key = IS_TOTAL_SYNC_COMPLETED).first() - if (isTotalSyncCompleted) { + if ( + mmConfigResponseHelper.journeySource == MMJourneySource.NAVI_UPI.name || + isTotalSyncCompleted + ) { val data = repository.getMonthSelectionBottomSheetData(displayMonthText) sendEvent( CategoryDetailsScreenUiEvent.ShowBottomSheet( @@ -163,7 +183,10 @@ constructor( viewModelScope.safeLaunch(Dispatchers.IO) { val isTotalSyncCompleted = dbDataStoreProvider.getBooleanData(key = IS_TOTAL_SYNC_COMPLETED).first() - if (isTotalSyncCompleted) { + if ( + mmConfigResponseHelper.journeySource == MMJourneySource.NAVI_UPI.name || + isTotalSyncCompleted + ) { sendEvent( CategoryDetailsScreenUiEvent.ShowBottomSheet( type = CategoryDetailsScreenBottomSheets.AverageInfo(averageValue) diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/model/DashboardData.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/model/DashboardData.kt index b7d48cf6d8..ff3f02c216 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/model/DashboardData.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/model/DashboardData.kt @@ -18,6 +18,7 @@ data class DashboardData( val bankSectionData: BankSectionData = BankSectionData(), val spendCategorizationState: SpendCategorizationState? = null, val recentTransactionsData: RecentTransactionsSectionData = RecentTransactionsSectionData(), + val feedbackSectionData: FeedbackSectionData = FeedbackSectionData(), ) data class OnboardingAnimationData( @@ -34,6 +35,16 @@ sealed interface OnboardingAnimationStatus { data class NavBarData(val actionLabel: String? = null) +data class FeedbackSectionData( + val title: String? = null, + val subtitle: String? = null, + val buttonLabel: String? = null, + val icon: IllustrationSource? = null, + val bottomSheetTitle: String? = null, +) + +data class FeedBackBottomSheetData(val title: String? = null) + sealed class UserHeaderState(val data: UserHeaderInfo) { data object Loading : UserHeaderState(UserHeaderInfo(isLoading = true)) @@ -156,4 +167,6 @@ sealed interface AddAccountState { data object RecentTransactionSection : Selected } + + data object None : AddAccountState } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/model/DashboardScreenUiEffect.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/model/DashboardScreenUiEffect.kt index 3170deca7e..fd0afecf7d 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/model/DashboardScreenUiEffect.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/model/DashboardScreenUiEffect.kt @@ -46,4 +46,6 @@ sealed class DashboardScreenUiEffect : UiEffect { data class OpenCategoryBottomSheet(val transaction: Transaction) : DashboardScreenUiEffect() data object FetchConsentUrl : DashboardScreenUiEffect() + + data class ShowToast(val message: String) : DashboardScreenUiEffect() } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/model/DashboardScreenUiEvent.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/model/DashboardScreenUiEvent.kt index 09aeaead17..284bc04edb 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/model/DashboardScreenUiEvent.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/model/DashboardScreenUiEvent.kt @@ -14,6 +14,11 @@ import com.navi.moneymanager.common.model.bottomSheet.DashboardScreenBottomSheet import com.navi.moneymanager.postonboard.help.model.HelpBottomSheetState sealed class DashboardScreenUiEvent : UiEvent { + data class UpdateDashboardData( + val spendCategorizationState: SpendCategorizationState, + val feedbackSectionData: FeedbackSectionData, + ) : DashboardScreenUiEvent() + data class UpdateDashboardTopNavBarData(val data: NavBarData) : DashboardScreenUiEvent() data class UpdateDashboardHeaderState(val state: UserHeaderState) : DashboardScreenUiEvent() @@ -67,4 +72,6 @@ sealed class DashboardScreenUiEvent : UiEvent { DashboardScreenUiEvent() data object CallToStopOnboardingAnimation : DashboardScreenUiEvent() + + data class UpdateFeedbackSectionData(val data: FeedbackSectionData) : DashboardScreenUiEvent() } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/reducer/DashboardScreenReducer.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/reducer/DashboardScreenReducer.kt index 1b12501f6a..b931e2a3fe 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/reducer/DashboardScreenReducer.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/reducer/DashboardScreenReducer.kt @@ -24,6 +24,16 @@ class DashboardScreenReducer : BaseReducer { + previousState.copy( + isLoading = false, + screenData = + previousState.screenData.copy( + spendCategorizationState = event.spendCategorizationState, + feedbackSectionData = event.feedbackSectionData, + ), + ) + } is DashboardScreenUiEvent.UpdateDashboardTopNavBarData -> { previousState.copy( screenData = previousState.screenData.copy(topNavBar = event.data) @@ -380,6 +390,12 @@ class DashboardScreenReducer : BaseReducer { + previousState.copy( + screenData = previousState.screenData.copy(feedbackSectionData = event.data) + ) + } } } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/repo/DashboardScreenRepository.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/repo/DashboardScreenRepository.kt index ea3c292a4e..fb2f6ed747 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/repo/DashboardScreenRepository.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/repo/DashboardScreenRepository.kt @@ -10,16 +10,24 @@ package com.navi.moneymanager.postonboard.dashboard.repo import androidx.compose.runtime.MutableState import com.navi.base.utils.EMPTY import com.navi.base.utils.orZero +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper import com.navi.moneymanager.common.dataprovider.data.datastore.DataStoreInfoProvider import com.navi.moneymanager.common.dataprovider.domain.DashboardDataProvider import com.navi.moneymanager.common.dataprovider.domain.RemoteDataProvider +import com.navi.moneymanager.common.dataprovider.domain.UpiSpendDashboardDataProvider import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQuery import com.navi.moneymanager.common.db.database.MMDatabase +import com.navi.moneymanager.common.db.upi.database.UpiSpendDatabase import com.navi.moneymanager.common.model.SelectedMonth +import com.navi.moneymanager.common.model.SpendCategorizationState +import com.navi.moneymanager.common.model.sectionHeader.MMJourneySource import com.navi.moneymanager.common.navigation.utils.MMScreen import com.navi.moneymanager.common.network.di.RoomDataStoreInfoProvider import com.navi.moneymanager.common.network.model.MMConfigResponse import com.navi.moneymanager.common.utils.Constants.USER_NAME +import com.navi.moneymanager.postonboard.dashboard.model.BankAccountsState +import com.navi.moneymanager.postonboard.dashboard.model.FeedbackSectionData +import com.navi.moneymanager.postonboard.dashboard.model.NavBarData import com.navi.moneymanager.postonboard.dashboard.model.UserHeaderState import dagger.Lazy import javax.inject.Inject @@ -33,7 +41,10 @@ class DashboardScreenRepository constructor( private val remoteDataProvider: RemoteDataProvider, private val dashboardDataProvider: DashboardDataProvider, + private val upiSpendDashboardDataProvider: UpiSpendDashboardDataProvider, private val database: Lazy, + private val upiSpendDatabase: Lazy, + private val mmConfigResponseHelper: MMConfigResponseHelper, @RoomDataStoreInfoProvider private val dbDataStoreProvider: DataStoreInfoProvider, ) { @@ -51,24 +62,55 @@ constructor( remoteDataProvider.fetchMMConfigResponse(screenName = MMScreen.DASHBOARD.screen) return networkResponse.data?.let { - executeQuery( - queryName = database.get().transactionsDao()::deleteTransactionsOlderThan.name, - methodName = ::fetchAndSaveConfigResponse.name, - query = { - database - .get() - .transactionsDao() - .deleteTransactionsOlderThan( - it.timestampConfig?.twelveMonthsOldTimestamp.orZero() - ) - }, - ) + when (mmConfigResponseHelper.journeySource) { + MMJourneySource.ACCOUNT_AGGREGATOR.name -> { + executeQuery( + queryName = + database.get().transactionsDao()::deleteTransactionsOlderThan.name, + methodName = ::fetchAndSaveConfigResponse.name, + query = { + database + .get() + .transactionsDao() + .deleteTransactionsOlderThan( + it.timestampConfig?.twelveMonthsOldTimestamp.orZero() + ) + }, + ) + } + MMJourneySource.NAVI_UPI.name -> { + executeQuery( + queryName = + upiSpendDatabase.get().transactionsDao()::deleteTransactionsOlderThan + .name, + methodName = ::fetchAndSaveConfigResponse.name, + query = { + upiSpendDatabase + .get() + .transactionsDao() + .deleteTransactionsOlderThan( + it.timestampConfig?.twelveMonthsOldTimestamp.orZero() + ) + }, + ) + } + } dashboardDataProvider.saveMMConfigToDB(it) dashboardDataProvider.getMMConfig() } ?: run { null } } - suspend fun getTopNavBarData() = dashboardDataProvider.getTopNavBarData() + suspend fun getTopNavBarData(): NavBarData { + return when (mmConfigResponseHelper.journeySource) { + MMJourneySource.NAVI_UPI.name -> { + upiSpendDashboardDataProvider.getTopNavBarData() + } + + else -> { + dashboardDataProvider.getTopNavBarData() + } + } + } fun getHeaderStateFlow(): Flow = flow { val storedName = dbDataStoreProvider.getStringData(USER_NAME, EMPTY).first() @@ -91,7 +133,8 @@ constructor( emit(userHeaderInfo) } - suspend fun getBankSectionStateFlow() = dashboardDataProvider.getBankSectionStateFlow() + suspend fun getBankSectionStateFlow(): Flow> = + dashboardDataProvider.getBankSectionStateFlow() suspend fun updateBankSectionRefreshingText(refreshing: Boolean) = dashboardDataProvider.updateBankSectionRefreshingText(refreshing) @@ -101,7 +144,17 @@ constructor( suspend fun getSpendCategorizationSectionStateFlow( screenParams: MutableStateFlow - ) = dashboardDataProvider.getSpendCategorizationSectionStateFlow(screenParams) + ): Flow { + return when (mmConfigResponseHelper.journeySource) { + MMJourneySource.NAVI_UPI.name -> { + upiSpendDashboardDataProvider.getSpendCategorizationSectionStateFlow(screenParams) + } + + else -> { + dashboardDataProvider.getSpendCategorizationSectionStateFlow(screenParams) + } + } + } suspend fun getRecentTransactionsSectionStateFlow(currentMonthTag: MutableState) = dashboardDataProvider.getRecentTransactionsSectionStateFlow(currentMonthTag) @@ -128,4 +181,16 @@ constructor( fun getRecentTransactionsLoadingState() = dashboardDataProvider.getRecentTransactionsLoadingState() + + suspend fun getFeedbackSectionData(): FeedbackSectionData { + return when (mmConfigResponseHelper.journeySource) { + MMJourneySource.NAVI_UPI.name -> { + upiSpendDashboardDataProvider.getFeedbackSectionData() + } + + else -> { + dashboardDataProvider.getFeedbackSectionData() + } + } + } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashBoardFinarkeinErrorBottomSheet.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashBoardFinarkeinErrorBottomSheet.kt index 762fa82a82..2b954a42c0 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashBoardFinarkeinErrorBottomSheet.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashBoardFinarkeinErrorBottomSheet.kt @@ -37,13 +37,11 @@ import com.navi.design.font.FontWeightEnum import com.navi.elex.molecules.ElexButtonWithLoader import com.navi.elex.theme.elexColors import com.navi.elex.theme.typography.TextTypography -import com.navi.moneymanager.common.analytics.DashboardEventTrackerImpl -import com.navi.moneymanager.common.analytics.ValuePropEventTrackerImpl +import com.navi.moneymanager.common.analytics.FinarkeinBottomSheetEventTracker import com.navi.moneymanager.common.illustration.model.IllustrationType import com.navi.moneymanager.common.illustration.model.ImageProperties import com.navi.moneymanager.common.illustration.ui.Illustration import com.navi.moneymanager.common.illustration.utils.lottieCompositionSpecBasedOnSource -import com.navi.moneymanager.common.navigation.utils.MMScreen import com.navi.moneymanager.common.ui.composable.base.MMText import com.navi.moneymanager.postonboard.dashboard.model.FinarkeinErrorBottomSheetData import com.navi.moneymanager.postonboard.dashboard.model.FinarkeinSheetStatus @@ -54,30 +52,13 @@ fun FinarkeinErrorBottomSheetContent( onRetry: () -> Unit, onDismiss: () -> Unit, data: FinarkeinErrorBottomSheetData, + getEventTracker: () -> FinarkeinBottomSheetEventTracker, ) { - LaunchedEffect(Unit) { - when (screenName) { - MMScreen.VALUE_PROP_SCREEN.screen -> { - ValuePropEventTrackerImpl.onValuePropFinarkeinErrorBottomSheetAppeared() - } - MMScreen.DASHBOARD.screen -> { - DashboardEventTrackerImpl.onDashboardFinarkeinErrorBottomSheetAppeared() - } - } - } + LaunchedEffect(Unit) { getEventTracker().onFinarkeinBottomSheetAppeared(screenName) } DisposableEffect(Unit) { - onDispose { - when (screenName) { - MMScreen.VALUE_PROP_SCREEN.screen -> { - ValuePropEventTrackerImpl.onValuePropFinarkeinErrorBottomSheetDisappeared() - } - MMScreen.DASHBOARD.screen -> { - DashboardEventTrackerImpl.onDashboardFinarkeinErrorBottomSheetDisappeared() - } - } - } + onDispose { getEventTracker().onFinarkeinBottomSheetDisappeared(screenName) } } Column( diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardContainerAnimatingEffect.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardContainerAnimatingEffect.kt index 14fcda2ee8..0fd0ac0a79 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardContainerAnimatingEffect.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardContainerAnimatingEffect.kt @@ -28,7 +28,6 @@ import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import com.navi.base.sharedpref.PreferenceManager -import com.navi.moneymanager.common.analytics.DashboardEventTrackerImpl import com.navi.moneymanager.common.utils.Constants.MM_IS_USER_ONBOARDED import com.navi.moneymanager.postonboard.dashboard.model.BankSectionData import com.navi.moneymanager.postonboard.dashboard.model.CurrentItemOnDashboardState @@ -57,9 +56,9 @@ internal fun DashboardContainerAnimatingEffect( headerSectionHeight: (height: Int) -> Unit, ) { LaunchedEffect(Unit) { - DashboardEventTrackerImpl.onDashboardAnimationStart( - PreferenceManager.getBooleanPreference(MM_IS_USER_ONBOARDED) - ) + getViewModel() + .dashboardEventTracker + .onDashboardAnimationStart(PreferenceManager.getBooleanPreference(MM_IS_USER_ONBOARDED)) } var currentState by remember { @@ -154,17 +153,17 @@ internal fun DashboardContainerAnimatingEffect( LaunchedEffect(currentState) { if (shouldStopOnBoardingAnimation) { - DashboardEventTrackerImpl.animationHasStoppedAfterBottomSheetSuccess() + getViewModel().dashboardEventTracker.animationHasStoppedAfterBottomSheetSuccess() if (currentState == CurrentItemOnDashboardState.EmptyState) { - DashboardEventTrackerImpl.markingSuccessForAnimationStatus( - currentState.javaClass.simpleName - ) + getViewModel() + .dashboardEventTracker + .markingSuccessForAnimationStatus(currentState.javaClass.simpleName) onEvent(DashboardScreenUiEvent.MarkOnBoardingAnimationStatusSuccess) return@LaunchedEffect } else { - DashboardEventTrackerImpl.markingSuccessForAnimationStatus( - currentState.javaClass.simpleName - ) + getViewModel() + .dashboardEventTracker + .markingSuccessForAnimationStatus(currentState.javaClass.simpleName) handleCurrentItemFadeOut(this) return@LaunchedEffect } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardFeedbackSection.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardFeedbackSection.kt new file mode 100644 index 0000000000..358da31a9d --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardFeedbackSection.kt @@ -0,0 +1,112 @@ +/* + * + * * Copyright © 2024-2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.postonboard.dashboard.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +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.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.navi.common.utils.onClickWithDebounce +import com.navi.design.font.FontWeightEnum +import com.navi.elex.theme.elexColors +import com.navi.moneymanager.common.illustration.model.IllustrationType +import com.navi.moneymanager.common.illustration.model.ImageProperties +import com.navi.moneymanager.common.illustration.ui.Illustration +import com.navi.moneymanager.common.ui.composable.base.MMText +import com.navi.moneymanager.common.utils.fourDpRoundedShape +import com.navi.moneymanager.postonboard.dashboard.model.FeedBackBottomSheetData +import com.navi.moneymanager.postonboard.dashboard.model.FeedbackSectionData + +@Composable +fun DashboardFeedbackSection( + data: FeedbackSectionData, + onShareFeedbackClick: (FeedBackBottomSheetData) -> Unit, +) { + Row( + modifier = + Modifier.fillMaxWidth() + .padding(start = 16.dp, end = 16.dp) + .border( + width = 1.dp, + color = elexColors.base.border.gray.standard, + shape = fourDpRoundedShape, + ) + .padding(all = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Column(modifier = Modifier.weight(1f, fill = false)) { + MMText( + text = data.title.orEmpty(), + color = elexColors.base.text.gray.standard, + fontSize = 14.sp, + fontWeight = FontWeightEnum.NAVI_HEADLINE_REGULAR, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + ) + Spacer(modifier = Modifier.height(4.dp)) + MMText( + text = data.subtitle.orEmpty(), + color = elexColors.base.text.gray.dim, + fontSize = 12.sp, + fontWeight = FontWeightEnum.NAVI_BODY_REGULAR, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + ) + Spacer(modifier = Modifier.height(12.dp)) + Box( + modifier = + Modifier.onClickWithDebounce { + onShareFeedbackClick( + FeedBackBottomSheetData(title = data.bottomSheetTitle.orEmpty()) + ) + } + .background( + color = elexColors.interactive.background.gray.default, + shape = RoundedCornerShape(4.dp), + ), + contentAlignment = Alignment.Center, + ) { + MMText( + text = data.buttonLabel.orEmpty(), + modifier = Modifier.padding(vertical = 12.dp, horizontal = 30.dp), + color = elexColors.interactive.text.primary.standard, + fontSize = 12.sp, + fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + ) + } + } + data.icon?.let { + Illustration( + illustrationType = + IllustrationType.Image( + source = it, + properties = ImageProperties(contentScale = ContentScale.FillWidth), + ), + modifier = Modifier.size(86.dp), + ) + } + } +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardOnboardingBottomSheetContent.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardOnboardingBottomSheetContent.kt index 3d6114240a..9640e9fc8e 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardOnboardingBottomSheetContent.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardOnboardingBottomSheetContent.kt @@ -73,6 +73,7 @@ fun DashboardOnboardingBottomSheetContent( onEvent: (DashboardScreenUiEvent) -> Unit, onEffect: (DashboardScreenUiEffect) -> Unit, pushNotificationPermission: MultiplePermissionsState, + getEventTracker: () -> DashboardEventTrackerImpl, ) { var currentProgress by remember { mutableFloatStateOf(0f) } var progressIndicatorVisibility by remember { mutableStateOf(true) } @@ -106,16 +107,17 @@ fun DashboardOnboardingBottomSheetContent( ) { when (it) { is OnboardingStatus.Loading -> { - DashboardLoadingBottomSheetContent() + DashboardLoadingBottomSheetContent(getEventTracker) } is OnboardingStatus.TakingLonger -> { DashboardTakingLongerBottomSheetContent( onEffect = onEffect, pushNotificationPermission = pushNotificationPermission, + getEventTracker = getEventTracker, ) } is OnboardingStatus.Success -> { - DashboardSuccessBottomSheetContent() + DashboardSuccessBottomSheetContent(getEventTracker) onEvent(DashboardScreenUiEvent.CallToStopOnboardingAnimation) } } @@ -124,14 +126,12 @@ fun DashboardOnboardingBottomSheetContent( } @Composable -fun DashboardLoadingBottomSheetContent() { +fun DashboardLoadingBottomSheetContent(getEventTracker: () -> DashboardEventTrackerImpl) { - LaunchedEffect(Unit) { - DashboardEventTrackerImpl.onDashboardOnboardingLoadingBottomSheetAppeared() - } + LaunchedEffect(Unit) { getEventTracker().onDashboardOnboardingLoadingBottomSheetAppeared() } DisposableEffect(Unit) { - onDispose { DashboardEventTrackerImpl.onDashboardOnboardingLoadingBottomSheetDisappeared() } + onDispose { getEventTracker().onDashboardOnboardingLoadingBottomSheetDisappeared() } } Column(modifier = Modifier.fillMaxWidth()) { Column( @@ -196,15 +196,12 @@ fun DashboardLoadingBottomSheetContent() { fun DashboardTakingLongerBottomSheetContent( onEffect: (DashboardScreenUiEffect) -> Unit, pushNotificationPermission: MultiplePermissionsState, + getEventTracker: () -> DashboardEventTrackerImpl, ) { - LaunchedEffect(Unit) { - DashboardEventTrackerImpl.onDashboardOnboardingTakingLongBottomSheetAppeared() - } + LaunchedEffect(Unit) { getEventTracker().onDashboardOnboardingTakingLongBottomSheetAppeared() } DisposableEffect(Unit) { - onDispose { - DashboardEventTrackerImpl.onDashboardOnboardingTakingLongBottomSheetDisappeared() - } + onDispose { getEventTracker().onDashboardOnboardingTakingLongBottomSheetDisappeared() } } Column(modifier = Modifier.fillMaxWidth()) { @@ -295,14 +292,12 @@ fun DashboardTakingLongerBottomSheetContent( } @Composable -fun DashboardSuccessBottomSheetContent() { +fun DashboardSuccessBottomSheetContent(getEventTracker: () -> DashboardEventTrackerImpl) { - LaunchedEffect(Unit) { - DashboardEventTrackerImpl.onDashboardOnboardingSuccessBottomSheetAppeared() - } + LaunchedEffect(Unit) { getEventTracker().onDashboardOnboardingSuccessBottomSheetAppeared() } DisposableEffect(Unit) { - onDispose { DashboardEventTrackerImpl.onDashboardOnboardingSuccessBottomSheetDisappeared() } + onDispose { getEventTracker().onDashboardOnboardingSuccessBottomSheetDisappeared() } } Column( diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardRecentTransactionsSection.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardRecentTransactionsSection.kt index 4346316b0c..5c4a0142a5 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardRecentTransactionsSection.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardRecentTransactionsSection.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.unit.sp import com.navi.alfred.utils.orFalse import com.navi.design.font.FontWeightEnum import com.navi.elex.theme.elexColors +import com.navi.moneymanager.common.analytics.TransactionHistoryEventTrackerImpl import com.navi.moneymanager.common.composable.bankselectionbottomsheet.ZeroTransactionView import com.navi.moneymanager.common.illustration.model.IllustrationType import com.navi.moneymanager.common.illustration.ui.Illustration @@ -56,6 +57,7 @@ internal fun DashboardRecentTransactionsSection( trackDashboardBankChipCarouselView: (Int) -> Unit, trackDashboardBankChipClick: (Int) -> Unit, trackDashboardAddAccountPillClick: () -> Unit, + transactionHistoryEventTracker: () -> TransactionHistoryEventTrackerImpl, ) { Column( modifier = Modifier.fillMaxWidth(), @@ -76,6 +78,7 @@ internal fun DashboardRecentTransactionsSection( trackDashboardBankChipCarouselView = trackDashboardBankChipCarouselView, trackDashboardBankChipClick = trackDashboardBankChipClick, trackDashboardAddAccountPillClick = trackDashboardAddAccountPillClick, + transactionHistoryEventTracker = transactionHistoryEventTracker, ) } is RecentTransactionsState.Loading -> { @@ -105,6 +108,7 @@ private fun RecentTransactionsLoadedUI( trackDashboardBankChipCarouselView: (Int) -> Unit, trackDashboardBankChipClick: (Int) -> Unit, trackDashboardAddAccountPillClick: () -> Unit, + transactionHistoryEventTracker: () -> TransactionHistoryEventTrackerImpl, ) { SectionTitle(title = data.sectionTitle) @@ -127,6 +131,7 @@ private fun RecentTransactionsLoadedUI( accountTransactions = data.accountTransactions, onTransactionClick = onTransactionClick, onTransactionCategoryClick = onTransactionCategoryClick, + transactionHistoryEventTracker = transactionHistoryEventTracker, ) PrimaryButton(title = data.primaryCtaTitle, onClick = onViewAllTransactionsClick) @@ -215,6 +220,7 @@ private fun TransactionWrapper( accountTransactions: List, onTransactionClick: (String) -> Unit, onTransactionCategoryClick: (Transaction) -> Unit, + transactionHistoryEventTracker: () -> TransactionHistoryEventTrackerImpl, ) { val transactions = if (selectedAccount == aggregateTransactions?.bankReferenceId) { @@ -234,6 +240,7 @@ private fun TransactionWrapper( transactions = transactions.transactions, onTransactionClick = onTransactionClick, onTransactionCategoryClick = onTransactionCategoryClick, + transactionHistoryEventTracker = transactionHistoryEventTracker, ) } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardScaffoldRenderer.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardScaffoldRenderer.kt index e197057b1b..8afd728928 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardScaffoldRenderer.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardScaffoldRenderer.kt @@ -55,7 +55,6 @@ import com.navi.common.utils.onClickWithDebounce import com.navi.design.font.FontWeightEnum import com.navi.elex.theme.elexColors import com.navi.moneymanager.R -import com.navi.moneymanager.common.analytics.DashboardEventTrackerImpl import com.navi.moneymanager.common.illustration.model.IllustrationSource import com.navi.moneymanager.common.illustration.model.IllustrationType import com.navi.moneymanager.common.illustration.model.ImageProperties @@ -63,6 +62,7 @@ import com.navi.moneymanager.common.illustration.repository.ImageRepository.Comp import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.IMAGE_TRANSPARENT_PLACEHOLDER_SMALL import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.NEW_TRANSACTION import com.navi.moneymanager.common.illustration.ui.Illustration +import com.navi.moneymanager.common.model.bottomSheet.DashboardScreenBottomSheets import com.navi.moneymanager.common.navigation.utils.MMScreen import com.navi.moneymanager.common.ui.composable.MMTopBar import com.navi.moneymanager.common.ui.composable.base.MMText @@ -128,7 +128,7 @@ fun DashboardScaffoldRenderer( navigationIcon = NaviWidgetsR.drawable.ic_cross_black, actionIconText = dashboardState().screenData.topNavBar.actionLabel, onActionClick = { - DashboardEventTrackerImpl.onDashboardHelpClicked() + getViewModel().dashboardEventTracker.onDashboardHelpClicked() onEffect(DashboardScreenUiEffect.Navigation.Help) }, onNavigationIconClick = { onEffect(DashboardScreenUiEffect.Navigation.Back) }, @@ -213,7 +213,13 @@ fun DashboardScaffoldRenderer( onEffect = onEffect, ) } - + DashboardFeedbackSection(dashboardState().screenData.feedbackSectionData) { + onEvent( + DashboardScreenUiEvent.ShowBottomSheet( + DashboardScreenBottomSheets.FeedbackBottomSheet(it) + ) + ) + } DashboardFooterSection() } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardScreen.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardScreen.kt index 1cfaf31d79..021cf35eb9 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardScreen.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardScreen.kt @@ -35,6 +35,7 @@ import com.navi.moneymanager.common.analytics.DashboardEventTrackerImpl import com.navi.moneymanager.common.dataprovider.data.datastore.DataStoreInfoProvider import com.navi.moneymanager.common.illustration.model.IllustrationSource import com.navi.moneymanager.common.illustration.model.IllustrationType +import com.navi.moneymanager.common.model.SelectedMonth import com.navi.moneymanager.common.model.bottomSheet.DashboardScreenBottomSheets import com.navi.moneymanager.common.navigation.utils.MMScreen import com.navi.moneymanager.common.ui.composable.DataLoadingBottomSheetContent @@ -45,6 +46,7 @@ import com.navi.moneymanager.common.utils.Constants import com.navi.moneymanager.common.utils.Constants.GREEN_TICK_MARK import com.navi.moneymanager.common.utils.Constants.IS_CONSENT_REVOKED import com.navi.moneymanager.common.utils.Constants.IS_TOTAL_SYNC_COMPLETED +import com.navi.moneymanager.common.utils.Constants.THANK_YOU_FOR_YOUR_FEEDBACK import com.navi.moneymanager.common.utils.Constants.TRANSACTION_ID import com.navi.moneymanager.common.utils.MMScreenEventLogger import com.navi.moneymanager.common.utils.ShowCustomToast @@ -68,6 +70,7 @@ import com.navi.moneymanager.preonboard.finarkein.model.FinarkeinDataState import com.navi.moneymanager.preonboard.launcher.ui.GenericErrorBottomSheet import com.navi.naviwidgets.utils.URL import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.annotation.NavHostParam import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.onEach @@ -78,6 +81,7 @@ fun DashboardScreen( activity: MMActivity, bundle: Bundle? = null, viewModel: DashboardViewModel = hiltViewModel(), + @NavHostParam selectedMonth: SelectedMonth, ) { val pushNotificationPermission = rememberMultiplePermissions(permissions = listOf(Manifest.permission.POST_NOTIFICATIONS)) { @@ -103,12 +107,13 @@ fun DashboardScreen( val showBottomSheet = remember { mutableStateOf(false) } var showTransactionUpdatedToast by remember { mutableStateOf(false) } var updatedTransactionCount by remember { mutableIntStateOf(0) } + val toastState by viewModel.toastState.collectAsStateWithLifecycle() ScreenInit(screenName = MMScreen.DASHBOARD.screen, activity = activity) MMScreenEventLogger( - onScreenLand = { DashboardEventTrackerImpl.onDashboardScreenLanded() }, - onScreenExit = { DashboardEventTrackerImpl.onDashboardScreenExit() }, + onScreenLand = { viewModel.dashboardEventTracker.onDashboardScreenLanded() }, + onScreenExit = { viewModel.dashboardEventTracker.onDashboardScreenExit() }, ) LaunchedEffect(Unit) { @@ -266,6 +271,10 @@ fun DashboardScreen( ) } + is DashboardScreenUiEffect.ShowToast -> { + viewModel.updateToastState(true, effect.message) + } + DashboardScreenUiEffect.FetchConsentUrl -> { viewModel.fetchConsentUrl() } @@ -274,7 +283,7 @@ fun DashboardScreen( .collect() } - LaunchedEffect(Unit) { viewModel.updateSpendAnalysisSection() } + LaunchedEffect(selectedMonth) { viewModel.updateSpendAnalysisSection(selectedMonth) } BackHandler { viewModel.setEffect { DashboardScreenUiEffect.Navigation.Back } } @@ -299,6 +308,7 @@ fun DashboardScreen( viewModel.updateSpendCategorizationSectionData(month.first, month.second) }, pushNotificationPermission = pushNotificationPermission, + dashboardEventTracker = viewModel.dashboardEventTracker, ) } @@ -334,6 +344,20 @@ fun DashboardScreen( ) showTransactionUpdatedToast = false } + if (toastState.isVisible) { + ShowCustomToast( + context = activity, + toastDuration = ToastDuration.LENGTH_SHORT, + content = { + CustomToastView( + message = toastState.message.orEmpty(), + illustrationType = + IllustrationType.Image(IllustrationSource.Resource(resId = GREEN_TICK_MARK)), + ) + }, + ) + viewModel.updateToastState(isVisible = false) + } } @OptIn(ExperimentalPermissionsApi::class) @@ -345,6 +369,7 @@ private fun DashboardBottomSheetContentHandler( onEffect: (DashboardScreenUiEffect) -> Unit, onMonthSelected: (Pair) -> Unit = {}, pushNotificationPermission: MultiplePermissionsState, + dashboardEventTracker: DashboardEventTrackerImpl, ) { if (bottomSheet.sheetData == null) return val onDismissAction = bottomSheet.createDismissAction(onEvent) @@ -355,11 +380,10 @@ private fun DashboardBottomSheetContentHandler( data = bottomSheet.data!!, onDismiss = { onDismissAction() }, trackBottomSheetAppears = { - DashboardEventTrackerImpl.onDashboardFetchingTransactionsBottomSheetAppeared() + dashboardEventTracker.onDashboardFetchingTransactionsBottomSheetAppeared() }, trackBottomSheetDisappears = { - DashboardEventTrackerImpl - .onDashboardFetchingTransactionsBottomSheetDisappeared() + dashboardEventTracker.onDashboardFetchingTransactionsBottomSheetDisappeared() }, ) } @@ -370,6 +394,7 @@ private fun DashboardBottomSheetContentHandler( data = bottomSheet.data!!, onDismiss = { onDismissAction() }, onMonthSelected = onMonthSelected, + getEventTracker = { dashboardEventTracker }, ) } @@ -379,6 +404,7 @@ private fun DashboardBottomSheetContentHandler( onEvent = onEvent, onEffect = onEffect, pushNotificationPermission = pushNotificationPermission, + getEventTracker = { dashboardEventTracker }, ) } @@ -395,11 +421,11 @@ private fun DashboardBottomSheetContentHandler( data = bottomSheet.data!!, onDismiss = { onDismissAction() }, trackBottomSheetAppears = { - DashboardEventTrackerImpl + dashboardEventTracker .onDashboardPastMonthCategoryRefreshInProgressBottomsheetAppeared() }, trackBottomSheetDisappears = { - DashboardEventTrackerImpl + dashboardEventTracker .onDashboardPastMonthCategoryRefreshInProgressBottomsheetDisappeared() }, ) @@ -418,6 +444,7 @@ private fun DashboardBottomSheetContentHandler( onEffect(DashboardScreenUiEffect.Navigation.DismissFinarkeinBottomSheet) onDismissAction() }, + getEventTracker = { dashboardEventTracker }, ) } @@ -440,6 +467,7 @@ private fun DashboardBottomSheetContentHandler( ) }, onDismiss = onDismissAction, + getEventTracker = { dashboardEventTracker }, ) } @@ -448,6 +476,7 @@ private fun DashboardBottomSheetContentHandler( data = bottomSheet.data!!, onDismiss = { onDismissAction() }, screenName = MMScreen.DASHBOARD.name, + getEventTracker = { dashboardEventTracker }, ) } @@ -456,10 +485,10 @@ private fun DashboardBottomSheetContentHandler( data = bottomSheet.data!!, onDismiss = { onDismissAction() }, trackBottomSheetAppears = { - DashboardEventTrackerImpl.onDashboardAddAccountLoadingBottomSheetAppeared() + dashboardEventTracker.onDashboardAddAccountLoadingBottomSheetAppeared() }, trackBottomSheetDisappears = { - DashboardEventTrackerImpl.onDashboardAddAccountLoadingBottomSheetDisappeared() + dashboardEventTracker.onDashboardAddAccountLoadingBottomSheetDisappeared() }, ) } @@ -473,8 +502,22 @@ private fun DashboardBottomSheetContentHandler( onDismissAction() onEffect(DashboardScreenUiEffect.Navigation.SpendGoalScreen) }, + getEventTracker = { dashboardEventTracker }, ) } + + is DashboardScreenBottomSheets.FeedbackBottomSheet -> { + FeedbackBottomSheet( + data = bottomSheet.data, + onSubmit = { + onDismissAction() + onEffect(DashboardScreenUiEffect.ShowToast(THANK_YOU_FOR_YOUR_FEEDBACK)) + dashboardEventTracker.onDashboardFeedback(it) + }, + getEventTracker = { dashboardEventTracker }, + ) + } + DashboardScreenBottomSheets.NoBottomSheet -> {} } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardSections.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardSections.kt index 96d6a48add..0634bb2231 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardSections.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/DashboardSections.kt @@ -16,7 +16,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import com.navi.base.utils.orZero -import com.navi.moneymanager.common.analytics.DashboardEventTrackerImpl import com.navi.moneymanager.common.model.FilterAttribute import com.navi.moneymanager.common.model.SpendCategorizationAction import com.navi.moneymanager.common.model.bottomSheet.DashboardScreenBottomSheets @@ -71,20 +70,21 @@ internal fun BankSection( }, onEvent = onEvent, trackDashboardBankChipCarouselView = { pillCount -> - DashboardEventTrackerImpl.onDashboardBankAccountsPillsViewed( - "bank_section", - pillCount, - ) + getViewModel() + .dashboardEventTracker + .onDashboardBankAccountsPillsViewed("bank_section", pillCount) }, trackDashboardBankChipClick = { pillRank -> - DashboardEventTrackerImpl.onDashboardBankAccountsPillsClicked( - "bank_section", - pillRank, - ) + getViewModel() + .dashboardEventTracker + .onDashboardBankAccountsPillsClicked("bank_section", pillRank) }, trackDashboardAddAccountPillClick = { - DashboardEventTrackerImpl.onDashboardAddAccountPillClicked("bank_section") + getViewModel() + .dashboardEventTracker + .onDashboardAddAccountPillClicked("bank_section") }, + getEventTracker = { getViewModel().dashboardEventTracker }, ) } } @@ -99,9 +99,7 @@ internal fun SpendAnalysisSection( Column { SpendCategorizationSection( screenName = MMScreen.DASHBOARD.screen, - isAddAccountSelected = - (dashboardState().addAccountState - is AddAccountState.Selected.SpentCategorizationSection), + addAccountState = dashboardState().addAccountState, spendCategorizationState = dashboardState().screenData.spendCategorizationState, spendCategorizationAction = { spendCategorizationAction -> when (spendCategorizationAction) { @@ -169,7 +167,9 @@ internal fun SpendAnalysisSection( ) ) } - DashboardEventTrackerImpl.onDashboardSelfTransferCategoryClicked() + getViewModel() + .dashboardEventTracker + .onDashboardSelfTransferCategoryClicked() onEffect( DashboardScreenUiEffect.Navigation.TransactionHistory( filterList = transactionFilterList @@ -178,7 +178,7 @@ internal fun SpendAnalysisSection( } is SpendCategorizationAction.OnMonthChange -> { - DashboardEventTrackerImpl.onDashboardMonthSelectionRequested() + getViewModel().dashboardEventTracker.onDashboardMonthSelectionRequested() getViewModel() .handleMonthlySummaryMonthChangeClick( spendCategorizationAction.displayMonthText @@ -186,7 +186,9 @@ internal fun SpendAnalysisSection( } is SpendCategorizationAction.ViewMoreCategories -> { - DashboardEventTrackerImpl.onDashboardCategorySectionViewMoreClicked() + getViewModel() + .dashboardEventTracker + .onDashboardCategorySectionViewMoreClicked() onEffect( DashboardScreenUiEffect.Navigation.SpendAnalysisScreen( selectedMonth = spendCategorizationAction.selectedMonth @@ -218,6 +220,7 @@ internal fun SpendAnalysisSection( } } }, + getEventTracker = { getViewModel().spendAnalysisEventTracker }, ) } } @@ -255,7 +258,7 @@ internal fun RecentTransaction( onEffect(DashboardScreenUiEffect.OpenCategoryBottomSheet(transaction)) }, onViewAllTransactionsClick = { - DashboardEventTrackerImpl.onDashboardViewAllTransactionsClicked() + getViewModel().dashboardEventTracker.onDashboardViewAllTransactionsClicked() onEffect( DashboardScreenUiEffect.Navigation.TransactionHistory( filterList = getViewModel().getTransactionFilterList() @@ -263,21 +266,20 @@ internal fun RecentTransaction( ) }, trackDashboardBankChipCarouselView = { pillCount -> - DashboardEventTrackerImpl.onDashboardBankAccountsPillsViewed( - "recent_transactions_section", - pillCount, - ) + getViewModel() + .dashboardEventTracker + .onDashboardBankAccountsPillsViewed("recent_transactions_section", pillCount) }, trackDashboardBankChipClick = { pillRank -> - DashboardEventTrackerImpl.onDashboardBankAccountsPillsClicked( - "recent_transactions_section", - pillRank, - ) + getViewModel() + .dashboardEventTracker + .onDashboardBankAccountsPillsClicked("recent_transactions_section", pillRank) }, trackDashboardAddAccountPillClick = { - DashboardEventTrackerImpl.onDashboardAddAccountPillClicked( - "recent_transactions_section" - ) + getViewModel() + .dashboardEventTracker + .onDashboardAddAccountPillClicked("recent_transactions_section") }, + transactionHistoryEventTracker = { getViewModel().transactionHistoryEventTracker }, ) } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/FeedbackBottomSheet.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/FeedbackBottomSheet.kt new file mode 100644 index 0000000000..1378fc7d51 --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/FeedbackBottomSheet.kt @@ -0,0 +1,138 @@ +/* + * + * * Copyright © 2024-2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.postonboard.dashboard.ui + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.ButtonDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.TextFieldValue +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.design.font.FontWeightEnum +import com.navi.elex.atoms.ElexButton +import com.navi.elex.theme.elexColors +import com.navi.moneymanager.R +import com.navi.moneymanager.common.analytics.DashboardEventTrackerImpl +import com.navi.moneymanager.common.ui.composable.base.MMText +import com.navi.moneymanager.common.ui.composable.base.MMTextField +import com.navi.moneymanager.postonboard.dashboard.model.FeedBackBottomSheetData +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +@Composable +fun FeedbackBottomSheet( + data: FeedBackBottomSheetData?, + onSubmit: (String) -> Unit, + getEventTracker: () -> DashboardEventTrackerImpl, +) { + var feedback by remember { mutableStateOf(TextFieldValue()) } + val focusRequester = remember { FocusRequester() } + val focusManager = LocalFocusManager.current + val coroutineScope = rememberCoroutineScope() + + val maxCharacters = 1000 + LaunchedEffect(Unit) { getEventTracker().onDashboardFeedbackBottomSheetAppeared() } + + Column( + modifier = + Modifier.fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 32.dp), + horizontalAlignment = Alignment.Start, + ) { + MMText( + text = data?.title.orEmpty(), + color = elexColors.base.text.gray.standard, + fontSize = 16.sp, + fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD, + letterSpacing = 0.sp, + lineHeight = 24.sp, + overflow = TextOverflow.Ellipsis, + ) + + Spacer(modifier = Modifier.height(24.dp)) + + MMTextField( + modifier = Modifier.fillMaxWidth().heightIn(min = 78.dp).focusRequester(focusRequester), + value = feedback, + onValueChange = { + if (it.text.length <= maxCharacters) { + feedback = it + } + }, + placeholderText = stringResource(R.string.feedback_bottom_sheet_input_placeholder), + onSuffixIllustrationClick = {}, + singleLine = false, + maxLines = 5, + keyboardOptions = + KeyboardOptions(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done), + ) + + Spacer(modifier = Modifier.height(40.dp)) + + ElexButton( + modifier = Modifier.fillMaxWidth(), + enabled = feedback.text.isNotEmpty(), + elevation = + ButtonDefaults.elevatedButtonElevation( + defaultElevation = 0.dp, + pressedElevation = 0.dp, + ), + shape = RoundedCornerShape(4.dp), + colors = + ButtonDefaults.buttonColors( + containerColor = elexColors.interactive.background.primary.default, + disabledContainerColor = elexColors.interactive.background.primary.disabled, + ), + onClick = { + focusManager.clearFocus() + coroutineScope.launch(Dispatchers.IO) { + delay(200) + onSubmit(feedback.text) + } + }, + contentPadding = PaddingValues(16.dp), + ) { + MMText( + text = stringResource(R.string.submit), + color = + if (feedback.text.isNotEmpty()) elexColors.interactive.text.gray.inverseStandard + else elexColors.interactive.text.primary.disabled, + fontSize = 14.sp, + fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD, + letterSpacing = 0.sp, + textAlign = TextAlign.Center, + lineHeight = 22.sp, + ) + } + } +} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/bankSection/BankSection.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/bankSection/BankSection.kt index d5e681a5b7..c18b169d80 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/bankSection/BankSection.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/bankSection/BankSection.kt @@ -10,7 +10,9 @@ package com.navi.moneymanager.postonboard.dashboard.ui.bankSection import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Column +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.shape.RoundedCornerShape import androidx.compose.material3.HorizontalDivider @@ -20,6 +22,7 @@ import androidx.compose.ui.draw.shadow import androidx.compose.ui.unit.dp import com.navi.base.utils.orFalse import com.navi.elex.theme.elexColors +import com.navi.moneymanager.common.analytics.DashboardEventTrackerImpl import com.navi.moneymanager.postonboard.dashboard.model.BankAccountsState import com.navi.moneymanager.postonboard.dashboard.model.BankSectionData import com.navi.moneymanager.postonboard.dashboard.model.DashboardScreenUiEvent @@ -34,8 +37,12 @@ fun BankSectionContent( trackDashboardBankChipCarouselView: (Int) -> Unit, trackDashboardBankChipClick: (Int) -> Unit, trackDashboardAddAccountPillClick: () -> Unit, + getEventTracker: () -> DashboardEventTrackerImpl, ) { - if (bankSectionData.state is BankAccountsState.None) return + if (bankSectionData.state is BankAccountsState.None) { + Spacer(modifier = Modifier.height(12.dp)) + return + } val borderColor = when (bankSectionData.state) { @@ -83,6 +90,7 @@ fun BankSectionContent( BankStatusSection( selectedBank = bankSectionData.selectedBank.orEmpty(), bankAccountsState = bankSectionData.state, + getEventTracker = getEventTracker, ) } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/bankSection/BankStatusSection.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/bankSection/BankStatusSection.kt index 82b86c2d63..2c5f005987 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/bankSection/BankStatusSection.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/ui/bankSection/BankStatusSection.kt @@ -34,11 +34,15 @@ import com.navi.moneymanager.postonboard.dashboard.model.BankAccountsState * @param selectedBank The reference ID of the currently selected bank. */ @Composable -fun BankStatusSection(bankAccountsState: BankAccountsState, selectedBank: String) { +fun BankStatusSection( + bankAccountsState: BankAccountsState, + selectedBank: String, + getEventTracker: () -> DashboardEventTrackerImpl, +) { LaunchedEffect(bankAccountsState is BankAccountsState.Loaded) { if (bankAccountsState is BankAccountsState.Loaded) { - DashboardEventTrackerImpl.onDashboardLastRefreshViewed() + getEventTracker().onDashboardLastRefreshViewed() } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/usecase/RefreshAndSyncDataUseCase.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/usecase/RefreshAndSyncDataUseCase.kt index fa308b2a76..eec8250f0d 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/usecase/RefreshAndSyncDataUseCase.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/usecase/RefreshAndSyncDataUseCase.kt @@ -11,6 +11,7 @@ import com.navi.base.utils.orFalse import com.navi.common.scheduler.TaskRepeater import com.navi.common.utils.safeLaunch import com.navi.moneymanager.common.analytics.DataSyncEventTrackerImpl +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper import com.navi.moneymanager.common.dataprovider.data.datastore.DataStoreInfoProvider import com.navi.moneymanager.common.dataprovider.domain.RemoteDataProvider import com.navi.moneymanager.common.datasync.DBSyncExecutor @@ -20,6 +21,7 @@ import com.navi.moneymanager.common.datasync.model.DataSyncState import com.navi.moneymanager.common.network.di.RoomDataStoreInfoProvider import com.navi.moneymanager.common.network.model.MMConfigResponse import com.navi.moneymanager.common.network.model.PollingStatusResponse +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE import com.navi.moneymanager.common.utils.Constants.LAST_REFRESH_SUCCESSFUL_TIMESTAMP import dagger.hilt.android.scopes.ViewModelScoped import javax.inject.Inject @@ -36,15 +38,22 @@ constructor( private val remoteDataProvider: RemoteDataProvider, @RoomDataStoreInfoProvider private val dbDataStoreProvider: DataStoreInfoProvider, private val dBSyncExecutor: DBSyncExecutor, + private val mmConfigResponseHelper: MMConfigResponseHelper, ) { private var taskRepeater: TaskRepeater? = null private val _isRefreshing = MutableStateFlow(false) val isRefreshing = _isRefreshing.asStateFlow() + private val dataSyncEventTracker by lazy { + DataSyncEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + suspend fun execute(scope: CoroutineScope, configResponse: MMConfigResponse) { val response = remoteDataProvider.refreshData() response.data?.requestId?.let { - DataSyncEventTrackerImpl.refreshStarted( + dataSyncEventTracker.refreshStarted( requestId = it, lastRefreshTimestamp = response.data?.lastRefreshSuccessTimestamp ?: 0L, ) @@ -62,8 +71,8 @@ constructor( return } taskRepeater = getTaskRepeater(requestId = requestId, configResponse = configResponse) - DataSyncEventTrackerImpl.periodicTaskSchedulerCreated() - DataSyncEventTrackerImpl.pollingStarted() + dataSyncEventTracker.periodicTaskSchedulerCreated() + dataSyncEventTracker.pollingStarted() taskRepeater?.startTask() } @@ -74,7 +83,7 @@ constructor( taskIntervalSeconds = configResponse.dataSyncPollingConfig?.taskIntervalSeconds ?: 5, onTimeout = { updateDataRefreshingState(false) - DataSyncEventTrackerImpl.pollingTimeout() + dataSyncEventTracker.pollingTimeout() }, task = { pollSyncStatus(requestId = requestId, configResponse) }, ) @@ -125,7 +134,7 @@ constructor( private fun stopPolling(updateRefreshingState: Boolean) { taskRepeater?.stopTask() taskRepeater = null - DataSyncEventTrackerImpl.pollingStopped() + dataSyncEventTracker.pollingStopped() if (updateRefreshingState) { updateDataRefreshingState(false) diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/viewmodel/DashboardViewModel.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/viewmodel/DashboardViewModel.kt index e9dc2e4f45..cfbb7a16c7 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/viewmodel/DashboardViewModel.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/dashboard/viewmodel/DashboardViewModel.kt @@ -13,17 +13,25 @@ import com.navi.base.sharedpref.PreferenceManager import com.navi.base.utils.BaseUtils.getDayMonthAndYearFromTimestamp import com.navi.common.network.models.isSuccessWithData import com.navi.moneymanager.base.viewmodel.MMBaseViewModel +import com.navi.moneymanager.common.analytics.DashboardEventTrackerImpl import com.navi.moneymanager.common.analytics.DataProviderEventTrackerImpl import com.navi.moneymanager.common.analytics.DataSyncEventTrackerImpl +import com.navi.moneymanager.common.analytics.SpendAnalysisEventTrackerImpl +import com.navi.moneymanager.common.analytics.TransactionHistoryEventTrackerImpl +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper import com.navi.moneymanager.common.dataprovider.data.datastore.DataStoreInfoProvider +import com.navi.moneymanager.common.datasync.UpiSpendDBSyncExecutor import com.navi.moneymanager.common.model.FilterAttribute +import com.navi.moneymanager.common.model.MMToastData import com.navi.moneymanager.common.model.SelectedMonth import com.navi.moneymanager.common.model.bottomSheet.DashboardScreenBottomSheets +import com.navi.moneymanager.common.model.sectionHeader.MMJourneySource import com.navi.moneymanager.common.navigation.utils.MMScreen import com.navi.moneymanager.common.network.di.RoomDataStoreInfoProvider import com.navi.moneymanager.common.utils.Constants.ALL_BANKS import com.navi.moneymanager.common.utils.Constants.IS_FIRST_MONTH_SYNC_COMPLETED import com.navi.moneymanager.common.utils.Constants.IS_TOTAL_SYNC_COMPLETED +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE import com.navi.moneymanager.common.utils.Constants.LAST_REFRESH_SUCCESSFUL_TIMESTAMP import com.navi.moneymanager.common.utils.Constants.MM_IS_USER_ONBOARDED import com.navi.moneymanager.common.utils.Constants.NEW_TRANSACTION_COUNT @@ -55,6 +63,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest @@ -69,15 +78,56 @@ constructor( private val refreshAndSyncDataUseCase: RefreshAndSyncDataUseCase, private val syncPreviousDataUseCase: SyncPreviousDataUseCase, private val manageConsentUseCase: ManageConsentUseCase, + val mmConfigResponseHelper: MMConfigResponseHelper, + private val upiDBSyncExecutor: UpiSpendDBSyncExecutor, ) : MMBaseViewModel( initialState = DashboardScreenUiState.initialState, reducer = DashboardScreenReducer(), ) { + val dataProviderEventTracker by lazy { + DataProviderEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + + val dashboardEventTracker by lazy { + DashboardEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + + private val dataSyncEventTracker by lazy { + DataSyncEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + + val transactionHistoryEventTracker by lazy { + TransactionHistoryEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + + val spendAnalysisEventTracker by lazy { + SpendAnalysisEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + + private val _toastState = MutableStateFlow(MMToastData(isVisible = false)) + val toastState = _toastState.asStateFlow() private val screenParams = MutableStateFlow(null) init { + when (mmConfigResponseHelper.journeySource) { + MMJourneySource.ACCOUNT_AGGREGATOR.name -> initMMDashboard() + MMJourneySource.NAVI_UPI.name -> initUpiSpendDashboard() + } + } + + private fun initMMDashboard() { PreferenceManager.setBooleanPreference(MM_IS_USER_ONBOARDED, true) loadDashboardScreenData() observeRefreshDataAndUpdateBankSectionLastUpdatedTime() @@ -121,12 +171,50 @@ constructor( syncDashboardData() } + private fun initUpiSpendDashboard() { + loadUpiSpendDashboardScreenData() + syncUpiSpendDashboardData() + } + private fun loadDashboardScreenData() { loadTopNavBar() loadUserHeader() loadBankSection() loadSpendAnalysisSection() loadRecentTransactionsSection() + loadFeedbackSection() + } + + private fun loadUpiSpendDashboardScreenData() { + sendEvent( + DashboardScreenUiEvent.HandleAddAccountState(addAccountState = AddAccountState.None) + ) + sendEvent(DashboardScreenUiEvent.MarkOnBoardingAnimationStatusSuccess) + sendEvent(DashboardScreenUiEvent.UpdateUserOnboardingStatus(true)) + sendEvent( + DashboardScreenUiEvent.UpdateOnBoardingBottomSheetStatus(OnboardingStatus.Success) + ) + loadUserHeader() + loadUpiSpendAnalysisAndFeedbackSection() + } + + private fun loadUpiSpendAnalysisAndFeedbackSection() { + viewModelScope.safeLaunch(Dispatchers.IO) { + repository + .getSpendCategorizationSectionStateFlow(screenParams = screenParams) + .collect { state -> + dataProviderEventTracker.dashboardVmSpendAnalysisSectionCollect( + stateName = state::class.simpleName.orEmpty() + ) + val feedbackSectionData = repository.getFeedbackSectionData() + sendEvent( + DashboardScreenUiEvent.UpdateDashboardData( + spendCategorizationState = state, + feedbackSectionData = feedbackSectionData, + ) + ) + } + } } private fun loadTopNavBar() { @@ -136,6 +224,13 @@ constructor( } } + private fun loadFeedbackSection() { + viewModelScope.safeLaunch(Dispatchers.IO) { + val feedbackSectionData = repository.getFeedbackSectionData() + sendEvent(DashboardScreenUiEvent.UpdateFeedbackSectionData(feedbackSectionData)) + } + } + private fun loadUserHeader() { viewModelScope.safeLaunch(Dispatchers.IO) { repository.getHeaderStateFlow().collect { userHeaderInfo -> @@ -147,7 +242,7 @@ constructor( private fun loadBankSection() { viewModelScope.safeLaunch(Dispatchers.IO) { repository.getBankSectionStateFlow().collect { (state, isCurrentMonthSynced) -> - DataProviderEventTrackerImpl.dashboardVmBankSectionCollect( + dataProviderEventTracker.dashboardVmBankSectionCollect( isFirstMonthSyncCompleted = isCurrentMonthSynced, bankSection = state::class.simpleName.orEmpty(), ) @@ -166,7 +261,7 @@ constructor( repository .getSpendCategorizationSectionStateFlow(screenParams = screenParams) .collect { state -> - DataProviderEventTrackerImpl.dashboardVmSpendAnalysisSectionCollect( + dataProviderEventTracker.dashboardVmSpendAnalysisSectionCollect( stateName = state::class.simpleName.orEmpty() ) sendEvent(DashboardScreenUiEvent.UpdateSpendCategorizationSectionState(state)) @@ -180,7 +275,7 @@ constructor( repository .getRecentTransactionsSectionStateFlow(currentMonthTag = currentMonthTag) .collect { state -> - DataProviderEventTrackerImpl.dashboardVmRecentTransactionSectionCollect( + dataProviderEventTracker.dashboardVmRecentTransactionSectionCollect( stateName = state::class.simpleName.orEmpty() ) sendEvent(DashboardScreenUiEvent.UpdateRecentTransactionsSectionState(state)) @@ -188,10 +283,10 @@ constructor( } } - fun updateSpendAnalysisSection() { + fun updateSpendAnalysisSection(selectedMonth: SelectedMonth) { updateSpendAnalysisSectionParams( - state.value.screenData.spendCategorizationState?.selectedMonth, - state.value.screenData.spendCategorizationState?.selectedYear, + selectedMonth.month ?: state.value.screenData.spendCategorizationState?.selectedMonth, + selectedMonth.year ?: state.value.screenData.spendCategorizationState?.selectedYear, ) } @@ -208,7 +303,7 @@ constructor( private fun updateSpendAnalysisSectionParams(month: Int? = null, year: Int? = null) { viewModelScope.safeLaunch(Dispatchers.IO) { - DataProviderEventTrackerImpl.dashboardVmSpendAnalysisParamsUpdate( + dataProviderEventTracker.dashboardVmSpendAnalysisParamsUpdate( month.toString(), year.toString(), ) @@ -239,22 +334,34 @@ constructor( fun handleMonthlySummaryMonthChangeClick(displayMonthText: String) { viewModelScope.safeLaunch(Dispatchers.IO) { - val pastMonthData = - dbDataStoreProvider.getBooleanData(key = IS_TOTAL_SYNC_COMPLETED).first() - if (!pastMonthData) { - val data = repository.getPastMonthDataLoadingBottomSheetData() - sendEvent( - DashboardScreenUiEvent.ShowBottomSheet( - type = DashboardScreenBottomSheets.PastMonthDataLoading(data) + when (mmConfigResponseHelper.journeySource) { + MMJourneySource.ACCOUNT_AGGREGATOR.name -> { + val pastMonthData = + dbDataStoreProvider.getBooleanData(key = IS_TOTAL_SYNC_COMPLETED).first() + if (!pastMonthData) { + val data = repository.getPastMonthDataLoadingBottomSheetData() + sendEvent( + DashboardScreenUiEvent.ShowBottomSheet( + type = DashboardScreenBottomSheets.PastMonthDataLoading(data) + ) + ) + } else { + val data = repository.getMonthSelectionBottomSheetData(displayMonthText) + sendEvent( + DashboardScreenUiEvent.ShowBottomSheet( + type = DashboardScreenBottomSheets.MonthSelection(data) + ) + ) + } + } + MMJourneySource.NAVI_UPI.name -> { + val data = repository.getMonthSelectionBottomSheetData(displayMonthText) + sendEvent( + DashboardScreenUiEvent.ShowBottomSheet( + type = DashboardScreenBottomSheets.MonthSelection(data) + ) ) - ) - } else { - val data = repository.getMonthSelectionBottomSheetData(displayMonthText) - sendEvent( - DashboardScreenUiEvent.ShowBottomSheet( - type = DashboardScreenBottomSheets.MonthSelection(data) - ) - ) + } } } } @@ -309,7 +416,7 @@ constructor( lastRefreshTimestamp = lastRefreshSuccessfulTimestamp, ) ) { - DataSyncEventTrackerImpl.rsFlowTriggered( + dataSyncEventTracker.rsFlowTriggered( currentTimestamp = configData.timestampConfig?.currentTimestamp.toString(), threshold = @@ -321,7 +428,7 @@ constructor( configResponse = configData, ) } else { - DataSyncEventTrackerImpl.sFlowTriggered( + dataSyncEventTracker.sFlowTriggered( currentTimestamp = configData.timestampConfig?.currentTimestamp.toString(), threshold = @@ -438,4 +545,14 @@ constructor( ) ) } + + fun updateToastState(isVisible: Boolean, message: String? = null) { + _toastState.update { it.copy(isVisible = isVisible, message = message ?: it.message) } + } + + private fun syncUpiSpendDashboardData() { + viewModelScope.safeLaunch(Dispatchers.IO) { + upiDBSyncExecutor.execute(MMScreen.DASHBOARD.screen) + } + } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/help/ui/HelpBottomSheet.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/help/ui/HelpBottomSheet.kt index 6c6977cd46..7b5bb1fe31 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/help/ui/HelpBottomSheet.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/help/ui/HelpBottomSheet.kt @@ -33,10 +33,7 @@ import com.navi.common.utils.onClickWithDebounce import com.navi.design.font.FontWeightEnum import com.navi.elex.theme.elexColors import com.navi.moneymanager.R -import com.navi.moneymanager.common.analytics.CategoryDetailsEventTrackerImpl -import com.navi.moneymanager.common.analytics.DashboardEventTrackerImpl -import com.navi.moneymanager.common.analytics.SpendAnalysisEventTrackerImpl -import com.navi.moneymanager.common.analytics.TransactionDetailsEventTrackerImpl +import com.navi.moneymanager.common.analytics.HelpBottomSheetEventTracker import com.navi.moneymanager.common.illustration.model.IllustrationSource import com.navi.moneymanager.common.illustration.model.IllustrationType import com.navi.moneymanager.common.illustration.model.ImageProperties @@ -45,7 +42,6 @@ import com.navi.moneymanager.common.illustration.repository.ImageRepository.Comp import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.FAQ_ICON import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.IMAGE_TRANSPARENT_PLACEHOLDER_SMALL import com.navi.moneymanager.common.illustration.ui.Illustration -import com.navi.moneymanager.common.navigation.utils.MMScreen import com.navi.moneymanager.common.ui.composable.base.MMDivider import com.navi.moneymanager.common.ui.composable.base.MMText import com.navi.moneymanager.common.utils.Constants.ERROR_RED_ALERT_ICON @@ -63,43 +59,13 @@ fun HelpBottomSheetContent( manageConsent: () -> Unit, navigateToUrl: (url: String?) -> Unit, onDismiss: () -> Unit, + getEventTracker: () -> HelpBottomSheetEventTracker, ) { - LaunchedEffect(Unit) { - when (screenName) { - MMScreen.DASHBOARD.screen -> { - DashboardEventTrackerImpl.onDashboardHelpBottomSheetAppeared() - } - MMScreen.TRANSACTION_DETAILS.screen -> { - TransactionDetailsEventTrackerImpl.onTransactionDetailsHelpBottomSheetAppeared() - } - MMScreen.CATEGORY_DETAILS.screen -> { - CategoryDetailsEventTrackerImpl.onCategoryDetailsHelpBottomSheetAppeared() - } - MMScreen.SPEND_ANALYSIS.screen -> { - SpendAnalysisEventTrackerImpl.onSpendAnalysisHelpBottomSheetAppeared() - } - } - } + LaunchedEffect(Unit) { getEventTracker().onHelpBottomSheetAppeared(screenName) } DisposableEffect(Unit) { - onDispose { - when (screenName) { - MMScreen.DASHBOARD.screen -> { - DashboardEventTrackerImpl.onDashboardHelpBottomSheetDisappeared() - } - MMScreen.TRANSACTION_DETAILS.screen -> { - TransactionDetailsEventTrackerImpl - .onTransactionDetailsHelpBottomSheetDisappeared() - } - MMScreen.CATEGORY_DETAILS.screen -> { - CategoryDetailsEventTrackerImpl.onCategoryDetailsHelpBottomSheetDisappeared() - } - MMScreen.SPEND_ANALYSIS.screen -> { - SpendAnalysisEventTrackerImpl.onSpendAnalysisHelpBottomSheetDisappeared() - } - } - } + onDispose { getEventTracker().onHelpBottomSheetDisappeared(screenName) } } LaunchedEffect(state) { @@ -128,21 +94,7 @@ fun HelpBottomSheetContent( placeholder = IMAGE_TRANSPARENT_PLACEHOLDER_SMALL, ), ) { - when (screenName) { - MMScreen.DASHBOARD.screen -> { - DashboardEventTrackerImpl.onDashboardHelpBottomSheetFaqClicked() - } - MMScreen.TRANSACTION_DETAILS.screen -> { - TransactionDetailsEventTrackerImpl - .onTransactionDetailsHelpBottomSheetFaqClicked() - } - MMScreen.CATEGORY_DETAILS.screen -> { - CategoryDetailsEventTrackerImpl.onCategoryDetailsHelpBottomSheetFaqClicked() - } - MMScreen.SPEND_ANALYSIS.screen -> { - SpendAnalysisEventTrackerImpl.onSpendAnalysisHelpBottomSheetFaqClicked() - } - } + getEventTracker().onHelpBottomSheetFaqClicked(screenName) onDismiss() onFaqClicked() } @@ -160,23 +112,7 @@ fun HelpBottomSheetContent( placeholder = IMAGE_TRANSPARENT_PLACEHOLDER_SMALL, ), ) { - when (screenName) { - MMScreen.DASHBOARD.screen -> { - DashboardEventTrackerImpl.onDashboardHelpBottomSheetManageConsentClicked() - } - MMScreen.TRANSACTION_DETAILS.screen -> { - TransactionDetailsEventTrackerImpl - .onTransactionDetailsHelpBottomSheetManageConsentClicked() - } - MMScreen.CATEGORY_DETAILS.screen -> { - CategoryDetailsEventTrackerImpl - .onCategoryDetailsHelpBottomSheetManageConsentClicked() - } - MMScreen.SPEND_ANALYSIS.screen -> { - SpendAnalysisEventTrackerImpl - .onSpendAnalysisHelpBottomSheetManageConsentClicked() - } - } + getEventTracker().onHelpBottomSheetManageConsentClicked(screenName) manageConsent() } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/monthlysummary/ui/bottomsheet/CategoryCommonBottomSheet.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/monthlysummary/ui/bottomsheet/CategoryCommonBottomSheet.kt index 44f6eaf349..71e68da547 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/monthlysummary/ui/bottomsheet/CategoryCommonBottomSheet.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/monthlysummary/ui/bottomsheet/CategoryCommonBottomSheet.kt @@ -58,14 +58,9 @@ import com.navi.elex.atoms.ElexBottomSheet import com.navi.elex.theme.elexColors import com.navi.moneymanager.R import com.navi.moneymanager.common.analytics.AddCategoryEventTrackerImpl -import com.navi.moneymanager.common.analytics.CategoryDetailsEventTrackerImpl -import com.navi.moneymanager.common.analytics.DashboardEventTrackerImpl -import com.navi.moneymanager.common.analytics.TransactionDetailsEventTrackerImpl -import com.navi.moneymanager.common.analytics.TransactionHistoryEventTrackerImpl import com.navi.moneymanager.common.illustration.model.IllustrationSource import com.navi.moneymanager.common.illustration.model.IllustrationType import com.navi.moneymanager.common.illustration.ui.Illustration -import com.navi.moneymanager.common.navigation.utils.MMScreen import com.navi.moneymanager.common.ui.composable.base.MMText import com.navi.moneymanager.common.ui.theme.color.MMColor import com.navi.moneymanager.common.utils.Constants @@ -150,7 +145,7 @@ fun CategoryCommonBottomSheet( bottomSheetUiState.postTransactionCategory?.let { when (it.state) { is PostTransactionCategoryState.Loading -> { - AddCategoryEventTrackerImpl.postTransactionCategoryLoading() + viewModel.addCategoryEventTracker.postTransactionCategoryLoading() viewModel.sendEvent( CategoryBottomSheetUiEvent.ToggleButtonLoader( isLoading = true, @@ -160,7 +155,7 @@ fun CategoryCommonBottomSheet( } is PostTransactionCategoryState.Error -> { - AddCategoryEventTrackerImpl.postTransactionCategoryError( + viewModel.addCategoryEventTracker.postTransactionCategoryError( errorCode = it.state.error?.statusCode.toString(), errorMessage = it.state.error?.message.toString(), ) @@ -174,7 +169,7 @@ fun CategoryCommonBottomSheet( } is PostTransactionCategoryState.Success -> { - AddCategoryEventTrackerImpl.postTransactionCategorySuccess() + viewModel.addCategoryEventTracker.postTransactionCategorySuccess() viewModel.sendEvent( CategoryBottomSheetUiEvent.ToggleButtonLoader( isLoading = false, @@ -292,6 +287,7 @@ fun CategoryCommonBottomSheet( currentContentType = currentContentType, showBottomSheet = showBottomSheet, bottomSheetUiState = bottomSheetUiState, + addCategoryEventTracker = viewModel.addCategoryEventTracker, ) } } @@ -347,50 +343,14 @@ fun AddCategoryBottomSheetContent( ) { LaunchedEffect(Unit) { - when (screenName) { - MMScreen.DASHBOARD.screen -> { - DashboardEventTrackerImpl.onDashboardCategorySelectionBottomSheetAppeared() - } - - MMScreen.CATEGORY_DETAILS.screen -> { - CategoryDetailsEventTrackerImpl - .onCategoryDetailsTransactionCategorySelectionBottomSheetAppeared() - } - - MMScreen.TRANSACTION_HISTORY.screen -> { - TransactionHistoryEventTrackerImpl - .onTransactionHistoryCategorySelectionBottomSheetAppeared() - } - - MMScreen.TRANSACTION_DETAILS.screen -> { - TransactionDetailsEventTrackerImpl - .onTransactionDetailsCategorySelectionBottomSheetAppeared() - } - } + viewModel.addCategoryEventTracker.onAddCategoryBottomSheetAppeared(screenName = screenName) } DisposableEffect(Unit) { onDispose { - when (screenName) { - MMScreen.DASHBOARD.screen -> { - DashboardEventTrackerImpl.onDashboardCategorySelectionBottomSheetDisappeared() - } - - MMScreen.CATEGORY_DETAILS.screen -> { - CategoryDetailsEventTrackerImpl - .onCategoryDetailsTransactionCategorySelectionBottomSheetDisappeared() - } - - MMScreen.TRANSACTION_HISTORY.screen -> { - TransactionHistoryEventTrackerImpl - .onTransactionHistoryCategorySelectionBottomSheetDisappeared() - } - - MMScreen.TRANSACTION_DETAILS.screen -> { - TransactionDetailsEventTrackerImpl - .onTransactionDetailsCategorySelectionBottomSheetDisappeared() - } - } + viewModel.addCategoryEventTracker.onAddCategoryBottomSheetDisappeared( + screenName = screenName + ) } } @@ -463,39 +423,12 @@ private fun onConfirmFooterClicked( changeContentType: (BottomSheetContentType) -> Unit, previousChip: Pair, ) { - when (screenName) { - MMScreen.DASHBOARD.screen -> { - DashboardEventTrackerImpl.onDashboardTransactionCategorySelectionApplied( - previousCategory = previousChip.first, - newCategory = selectedChip.first, - ) - } - - MMScreen.CATEGORY_DETAILS.screen -> { - CategoryDetailsEventTrackerImpl.onCategoryDetailsTransactionCategorySelectionApplied( - previousCategory = previousChip.first, - newCategory = selectedChip.first, - ) - } - - MMScreen.TRANSACTION_HISTORY.screen -> { - TransactionHistoryEventTrackerImpl - .onTransactionHistoryTransactionCategorySelectionApplied( - previousCategory = previousChip.first, - newCategory = selectedChip.first, - ) - } - - MMScreen.TRANSACTION_DETAILS.screen -> { - TransactionDetailsEventTrackerImpl - .onTransactionDetailsTransactionCategorySelectionApplied( - previousCategory = previousChip.first, - newCategory = selectedChip.first, - ) - } - } - - AddCategoryEventTrackerImpl.addCategoryConfirmFooterButtonClicked(selectedChip.first) + viewModel.addCategoryEventTracker.onTransactionCategorySelectionApplied( + previousCategory = previousChip.first, + newCategory = selectedChip.first, + screenName = screenName, + ) + viewModel.addCategoryEventTracker.addCategoryConfirmFooterButtonClicked(selectedChip.first) if (data.hasOnlyOneSimilarTransaction.orFalse()) { viewModel.postCategoryTypeForTransaction( callerType = CategoryBottomSheetButtonType.ADD_CATEGORY_CONFIRM, @@ -534,52 +467,16 @@ fun SimilarTransactionBottomSheetContent( ) { LaunchedEffect(Unit) { - when (screenName) { - MMScreen.DASHBOARD.screen -> { - DashboardEventTrackerImpl - .onDashboardCategoriseSimilarTransactionsBottomSheetAppeared() - } - - MMScreen.CATEGORY_DETAILS.screen -> { - CategoryDetailsEventTrackerImpl - .onCategoryDetailsCategoriseSimilarTransactionBottomSheetAppeared() - } - - MMScreen.TRANSACTION_HISTORY.screen -> { - TransactionHistoryEventTrackerImpl - .onTransactionHistoryCategoriseSimilarTransactionBottomSheetAppeared() - } - - MMScreen.TRANSACTION_DETAILS.screen -> { - TransactionDetailsEventTrackerImpl - .onTransactionDetailsCategoriseSimilarTransactionBottomSheetAppeared() - } - } + viewModel.addCategoryEventTracker.onCategoriseSimilarTransactionsBottomSheetAppeared( + screenName + ) } DisposableEffect(Unit) { onDispose { - when (screenName) { - MMScreen.DASHBOARD.screen -> { - DashboardEventTrackerImpl - .onDashboardCategoriseSimilarTransactionsBottomSheetDisappeared() - } - - MMScreen.CATEGORY_DETAILS.screen -> { - CategoryDetailsEventTrackerImpl - .onCategoryDetailsCategoriseSimilarTransactionBottomSheetDisappeared() - } - - MMScreen.TRANSACTION_HISTORY.screen -> { - TransactionHistoryEventTrackerImpl - .onTransactionHistoryCategoriseSimilarTransactionBottomSheetDisappeared() - } - - MMScreen.TRANSACTION_DETAILS.screen -> { - TransactionDetailsEventTrackerImpl - .onTransactionDetailsCategoriseSimilarTransactionBottomSheetDisappeared() - } - } + viewModel.addCategoryEventTracker.onCategoriseSimilarTransactionsBottomSheetAppeared( + screenName + ) } } @@ -611,6 +508,7 @@ fun SimilarTransactionBottomSheetContent( buttonType = CategoryBottomSheetButtonType.ADD_CATEGORY_CONFIRM, ) ) + viewModel.addCategoryEventTracker.similarTxnHeaderBackButtonClicked() onHeaderBackButtonClicked(changeContentType) }, ) @@ -669,37 +567,14 @@ private fun onSkipFooterButtonClicked( selectedCategoryId: String, categoryTransactionData: CategoryBottomSheetTransactionData, ): () -> Unit = { - AddCategoryEventTrackerImpl.proceedFooterButtonClicked( + viewModel.addCategoryEventTracker.proceedFooterButtonClicked( isAutoCategoriseChecked = isAutoCategoriseChecked.toString(), buttonType = CategoryBottomSheetButtonType.SIMILAR_TRANSACTION_SKIP.toString(), ) - when (screenName) { - MMScreen.DASHBOARD.screen -> { - DashboardEventTrackerImpl.onDashboardCategoriseSimilarTransactionSkipped( - category = selectedCategoryId - ) - } - - MMScreen.CATEGORY_DETAILS.screen -> { - CategoryDetailsEventTrackerImpl.onCategoryDetailsCategoriseSimilarTransactionSkipped( - category = selectedCategoryId - ) - } - - MMScreen.TRANSACTION_HISTORY.screen -> { - TransactionHistoryEventTrackerImpl - .onTransactionHistoryCategoriseSimilarTransactionSkipped( - category = selectedCategoryId - ) - } - - MMScreen.TRANSACTION_DETAILS.screen -> { - TransactionDetailsEventTrackerImpl - .onTransactionDetailsCategoriseSimilarTransactionSkipped( - category = selectedCategoryId - ) - } - } + viewModel.addCategoryEventTracker.onCategoriseSimilarTransactionSkipped( + category = selectedCategoryId, + screenName = screenName, + ) val overriddenTransaction = listOf( OverriddenTransaction( @@ -738,7 +613,7 @@ private fun onProceedFooterButtonClick( viewModel: CategoryBottomSheetViewModel, categoryTransactionData: CategoryBottomSheetTransactionData, ): () -> Unit = { - AddCategoryEventTrackerImpl.proceedFooterButtonClicked( + viewModel.addCategoryEventTracker.proceedFooterButtonClicked( isAutoCategoriseChecked = isAutoCategoriseChecked.toString(), buttonType = CategoryBottomSheetButtonType.SIMILAR_TRANSACTION_PROCEED.toString(), ) @@ -768,45 +643,13 @@ private fun onProceedFooterButtonClick( null }, ) - when (screenName) { - MMScreen.DASHBOARD.screen -> { - DashboardEventTrackerImpl.onDashboardCategoriseSimilarTransactionConfirmed( - isAutoCategoriseChecked, - areAllItemsSelected, - selectedItems.size, - selectedChip, - ) - } - - MMScreen.CATEGORY_DETAILS.screen -> { - CategoryDetailsEventTrackerImpl.onCategoryDetailsCategoriseSimilarTransactionConfirmed( - isAutoCategoriseChecked, - areAllItemsSelected, - selectedItems.size, - selectedChip, - ) - } - - MMScreen.TRANSACTION_HISTORY.screen -> { - TransactionHistoryEventTrackerImpl - .onTransactionHistoryCategoriseSimilarTransactionConfirmed( - isAutoCategoriseChecked, - areAllItemsSelected, - selectedItems.size, - selectedChip, - ) - } - - MMScreen.TRANSACTION_DETAILS.screen -> { - TransactionDetailsEventTrackerImpl - .onTransactionDetailsCategoriseSimilarTransactionConfirmed( - isAutoCategoriseChecked, - areAllItemsSelected, - selectedItems.size, - selectedChip, - ) - } - } + viewModel.addCategoryEventTracker.onCategoriseSimilarTransactionConfirmed( + isAutoCategoriseChecked = isAutoCategoriseChecked, + allTransactionsSelected = areAllItemsSelected, + numberOfTransactionsSelected = selectedItems.size, + category = selectedChip, + screenName = screenName, + ) viewModel.postCategoryTypeForTransaction( postCategoryTransactionData = postCategoryTransactionData, callerType = CategoryBottomSheetButtonType.SIMILAR_TRANSACTION_PROCEED, @@ -822,7 +665,6 @@ private fun getTransactionList( } private fun onHeaderBackButtonClicked(changeContentType: (BottomSheetContentType) -> Unit) { - AddCategoryEventTrackerImpl.similarTxnHeaderBackButtonClicked() changeContentType(BottomSheetContentType.AddCategoryBottomSheetContent) } @@ -831,9 +673,10 @@ private fun HandleBottomSheetBackNavigation( currentContentType: MutableState, showBottomSheet: MutableState, bottomSheetUiState: CategoryBottomSheetUiState, + addCategoryEventTracker: AddCategoryEventTrackerImpl, ) { - AddCategoryEventTrackerImpl.systemBackButtonClicked(currentContentType.value.toString()) + addCategoryEventTracker.systemBackButtonClicked(currentContentType.value.toString()) if (isLoading(bottomSheetUiState).not()) { BackHandler { if ( diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/monthlysummary/viewmodel/CategoryBottomSheetViewModel.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/monthlysummary/viewmodel/CategoryBottomSheetViewModel.kt index 8929284cbd..bf270d26d4 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/monthlysummary/viewmodel/CategoryBottomSheetViewModel.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/monthlysummary/viewmodel/CategoryBottomSheetViewModel.kt @@ -10,6 +10,9 @@ package com.navi.moneymanager.postonboard.monthlysummary.viewmodel import androidx.lifecycle.viewModelScope import com.navi.common.utils.isValidResponse import com.navi.moneymanager.base.viewmodel.MMBaseViewModel +import com.navi.moneymanager.common.analytics.AddCategoryEventTrackerImpl +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE import com.navi.moneymanager.postonboard.monthlysummary.model.CategoryBottomSheetButtonType import com.navi.moneymanager.postonboard.monthlysummary.model.CategoryBottomSheetUiEffect import com.navi.moneymanager.postonboard.monthlysummary.model.CategoryBottomSheetUiEvent @@ -31,6 +34,7 @@ class CategoryBottomSheetViewModel constructor( @Assisted private val screenName: String, private val repository: CategoryDetailsRepository, + val mmConfigResponseHelper: MMConfigResponseHelper, ) : MMBaseViewModel< CategoryBottomSheetUiState, @@ -41,6 +45,12 @@ constructor( reducer = CategoryBottomSheetReducer(), ) { + val addCategoryEventTracker by lazy { + AddCategoryEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + @AssistedFactory interface Factory { fun create(screenName: String): CategoryBottomSheetViewModel diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/model/SpendAnalysisScreenData.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/model/SpendAnalysisScreenData.kt index 39bfa74a85..5e95da1a39 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/model/SpendAnalysisScreenData.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/model/SpendAnalysisScreenData.kt @@ -10,6 +10,7 @@ package com.navi.moneymanager.postonboard.spendanalysis.model import com.navi.moneymanager.common.illustration.model.IllustrationSource import com.navi.moneymanager.common.model.BarGraphData import com.navi.moneymanager.common.model.SpendCategorizationState +import com.navi.moneymanager.common.model.SpendGoalState import com.navi.moneymanager.common.model.sectionHeader.SectionHeaderData import com.navi.moneymanager.postonboard.categorydetails.model.CategoryTotalSpendSectionCategoryDataData import com.navi.moneymanager.postonboard.dashboard.model.NavBarData @@ -31,8 +32,7 @@ data class TotalSpendSectionData( val selectedMonth: String, val actionIcon: IllustrationSource, val amount: String, - val progress: Double? = null, - val spendGoal: Double? = null, + val spendGoalState: SpendGoalState = SpendGoalState.None, val selectedBankReferenceIds: Set, val spendingTrendSectionData: SectionHeaderData? = null, val category: CategoryTotalSpendSectionCategoryDataData? = null, diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/repo/SpendAnalysisRepository.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/repo/SpendAnalysisRepository.kt index e2ac03271b..8adad14f5d 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/repo/SpendAnalysisRepository.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/repo/SpendAnalysisRepository.kt @@ -7,19 +7,44 @@ package com.navi.moneymanager.postonboard.spendanalysis.repo +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper import com.navi.moneymanager.common.dataprovider.domain.SpendAnalysisLocalDataProvider +import com.navi.moneymanager.common.dataprovider.domain.upi.UpiSpendAnalysisLocalDataProvider +import com.navi.moneymanager.common.model.DailySpendBottomSheetData +import com.navi.moneymanager.common.model.sectionHeader.MMJourneySource +import com.navi.moneymanager.postonboard.spendanalysis.model.OtherCategoriesBottomSheetData +import com.navi.moneymanager.postonboard.spendanalysis.model.SpendAnalysisScreenData import com.navi.moneymanager.postonboard.spendanalysis.model.dailySpendingData.DailySpendingData import javax.inject.Inject +import kotlinx.coroutines.flow.Flow class SpendAnalysisRepository @Inject -constructor(private val spendAnalysisDataProvider: SpendAnalysisLocalDataProvider) { +constructor( + private val spendAnalysisDataProvider: SpendAnalysisLocalDataProvider, + private val upiSpendAnalysisLocalDataProvider: UpiSpendAnalysisLocalDataProvider, + private val mmConfigResponseHelper: MMConfigResponseHelper, +) { suspend fun getSpendAnalysisScreenData( month: Int?, year: Int?, selectedBankReferenceIds: Set?, - ) = spendAnalysisDataProvider.getSpendAnalysisScreenData(month, year, selectedBankReferenceIds) + ): Flow { + return when (mmConfigResponseHelper.journeySource) { + MMJourneySource.NAVI_UPI.name -> { + upiSpendAnalysisLocalDataProvider.getSpendAnalysisScreenData(month, year) + } + + else -> { + spendAnalysisDataProvider.getSpendAnalysisScreenData( + month, + year, + selectedBankReferenceIds, + ) + } + } + } suspend fun getMonthSelectionBottomSheetData(displayMonthText: String) = spendAnalysisDataProvider.getMonthSelectionBottomSheetData(displayMonthText) @@ -31,16 +56,36 @@ constructor(private val spendAnalysisDataProvider: SpendAnalysisLocalDataProvide month: Int?, year: Int?, selectedBankReferenceIds: Set?, - ) = - spendAnalysisDataProvider.getOtherCategoriesBottomSheetData( - month, - year, - selectedBankReferenceIds, - ) + ): OtherCategoriesBottomSheetData { + return when (mmConfigResponseHelper.journeySource) { + MMJourneySource.NAVI_UPI.name -> { + upiSpendAnalysisLocalDataProvider.getOtherCategoriesBottomSheetData(month, year) + } + + else -> { + spendAnalysisDataProvider.getOtherCategoriesBottomSheetData( + month, + year, + selectedBankReferenceIds, + ) + } + } + } suspend fun getPastMonthDataLoadingBottomSheetData() = spendAnalysisDataProvider.getPastMonthDataLoadingBottomSheetData() - suspend fun getDailySpendBottomSheetData(dailySpendData: DailySpendingData.DailySpend) = - spendAnalysisDataProvider.getDailySpendBottomSheetData(dailySpendData) + suspend fun getDailySpendBottomSheetData( + dailySpendData: DailySpendingData.DailySpend + ): DailySpendBottomSheetData { + return when (mmConfigResponseHelper.journeySource) { + MMJourneySource.NAVI_UPI.name -> { + upiSpendAnalysisLocalDataProvider.getDailySpendBottomSheetData(dailySpendData) + } + + else -> { + spendAnalysisDataProvider.getDailySpendBottomSheetData(dailySpendData) + } + } + } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/ui/DailySpendBottomSheetContent.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/ui/DailySpendBottomSheetContent.kt index 33d111808a..07ec6e2c07 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/ui/DailySpendBottomSheetContent.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/ui/DailySpendBottomSheetContent.kt @@ -38,7 +38,7 @@ import androidx.compose.ui.unit.sp import com.navi.design.font.FontWeightEnum import com.navi.elex.atoms.ElexButton import com.navi.elex.theme.elexColors -import com.navi.moneymanager.common.analytics.SpendAnalysisEventTrackerImpl +import com.navi.moneymanager.common.analytics.TransactionHistoryEventTrackerImpl import com.navi.moneymanager.common.illustration.model.IllustrationType import com.navi.moneymanager.common.illustration.model.ImageProperties import com.navi.moneymanager.common.illustration.ui.Illustration @@ -52,6 +52,7 @@ import com.navi.moneymanager.common.utils.fourDpRoundedShape import com.navi.moneymanager.common.utils.rememberEmptyNestedScrollConnectionForColumn import com.navi.moneymanager.common.utils.rippleClickable import com.navi.moneymanager.common.utils.verticalShadow +import com.navi.moneymanager.postonboard.spendanalysis.viewmodel.SpendAnalysisVM @Composable fun DailySpendBottomSheetContent( @@ -60,14 +61,17 @@ fun DailySpendBottomSheetContent( onTransactionCategoryClick: (Transaction) -> Unit, onViewAllTransactionsClick: () -> Unit, onDismiss: () -> Unit, + getViewModel: () -> SpendAnalysisVM, ) { LaunchedEffect(Unit) { - SpendAnalysisEventTrackerImpl.onSpendAnalysisDailySpendBottomSheetAppeared() + getViewModel().spendAnalysisEventTracker.onSpendAnalysisDailySpendBottomSheetAppeared() } DisposableEffect(Unit) { onDispose { - SpendAnalysisEventTrackerImpl.onSpendAnalysisDailySpendBottomSheetDisappeared() + getViewModel() + .spendAnalysisEventTracker + .onSpendAnalysisDailySpendBottomSheetDisappeared() } } @@ -95,11 +99,14 @@ fun DailySpendBottomSheetContent( transactions = data.transactions, onTransactionClick = onTransactionClick, onTransactionCategoryClick = onTransactionCategoryClick, + transactionHistoryEventTracker = { getViewModel().transactionHistoryEventTracker }, ) - DailySpendBottomSheetFooterSection( - ctaText = data.ctaText, - onViewAllTransactionsClick = onViewAllTransactionsClick, - ) + if (data.isFooterVisible) { + DailySpendBottomSheetFooterSection( + ctaText = data.ctaText, + onViewAllTransactionsClick = onViewAllTransactionsClick, + ) + } } } @@ -159,6 +166,7 @@ fun DailySpendBottomSheetContentSection( transactions: List? = null, onTransactionClick: (String) -> Unit, onTransactionCategoryClick: (Transaction) -> Unit, + transactionHistoryEventTracker: () -> TransactionHistoryEventTrackerImpl, ) { if (transactions != null) { Column(modifier = modifier.fillMaxWidth()) { @@ -167,6 +175,7 @@ fun DailySpendBottomSheetContentSection( transactions = transactions, onTransactionClick = onTransactionClick, onTransactionCategoryClick = onTransactionCategoryClick, + transactionHistoryEventTracker = transactionHistoryEventTracker, ) } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/ui/OtherCategoriesBottomSheet.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/ui/OtherCategoriesBottomSheet.kt index f41dfc4b63..dbbc1816a9 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/ui/OtherCategoriesBottomSheet.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/ui/OtherCategoriesBottomSheet.kt @@ -61,17 +61,15 @@ fun OtherCategoriesBottomSheetContent( onConfirm: () -> Unit, trackCategoryItemClick: (Int, String) -> Unit, trackCategoryItemView: (Int, String) -> Unit, + getEventTracker: () -> SpendAnalysisEventTrackerImpl, ) { LaunchedEffect(Unit) { - SpendAnalysisEventTrackerImpl.onSpendAnalysisScreenOtherCategoriesBottomSheetAppeared() + getEventTracker().onSpendAnalysisScreenOtherCategoriesBottomSheetAppeared() } DisposableEffect(Unit) { - onDispose { - SpendAnalysisEventTrackerImpl - .onSpendAnalysisScreenOtherCategoriesBottomSheetDisappeared() - } + onDispose { getEventTracker().onSpendAnalysisScreenOtherCategoriesBottomSheetDisappeared() } } Column(modifier = Modifier.fillMaxWidth().padding(bottom = 32.dp)) { diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/ui/SpendAnalysisScaffoldRenderer.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/ui/SpendAnalysisScaffoldRenderer.kt index a11555142d..dd45dee451 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/ui/SpendAnalysisScaffoldRenderer.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/ui/SpendAnalysisScaffoldRenderer.kt @@ -32,7 +32,6 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import com.navi.base.utils.EMPTY import com.navi.common.R -import com.navi.common.constants.HELP_CTA_TEXT import com.navi.elex.theme.elexColors import com.navi.moneymanager.common.analytics.SpendAnalysisEventTrackerImpl import com.navi.moneymanager.common.illustration.model.IllustrationSource @@ -53,6 +52,7 @@ import com.navi.moneymanager.common.ui.composable.sectionHeaders.SectionHeader import com.navi.moneymanager.common.ui.composable.sectionHeaders.TopSectionForMonthAndBankSelection import com.navi.moneymanager.common.ui.composable.spendCategoriztion.SpendCategorizationSection import com.navi.moneymanager.common.utils.Constants.HEADER_SCROLL_TWEEN +import com.navi.moneymanager.postonboard.dashboard.model.AddAccountState import com.navi.moneymanager.postonboard.spendanalysis.model.SpendAnalysisScreenUiEffect import com.navi.moneymanager.postonboard.spendanalysis.model.SpendAnalysisScreenUiEvent import com.navi.moneymanager.postonboard.spendanalysis.model.SpendAnalysisScreenUiState @@ -74,6 +74,7 @@ fun SpendAnalysisScaffoldRenderer( selectedMonth: SelectedMonth, onEvent: (SpendAnalysisScreenUiEvent) -> Unit, onEffect: (SpendAnalysisScreenUiEffect) -> Unit, + getEventTracker: () -> SpendAnalysisEventTrackerImpl, ) { val screenData = remember(spendAnalysisScreenUiState) { spendAnalysisScreenUiState.screenData } @@ -101,11 +102,12 @@ fun SpendAnalysisScaffoldRenderer( }, titleColor = elexColors.interactive.text.primary.standard, navigationIcon = R.drawable.ic_arrow_left_black_v2, - actionIconText = if (!showScrolledStateTopSection) HELP_CTA_TEXT else null, + actionIconText = + if (!showScrolledStateTopSection) screenData?.topNavBar?.actionLabel else null, onActionClick = if (!showScrolledStateTopSection) { { - SpendAnalysisEventTrackerImpl.onSpendAnalysisScreenHelpClicked() + getEventTracker().onSpendAnalysisScreenHelpClicked() onEffect(SpendAnalysisScreenUiEffect.Navigation.Help) } } else null, @@ -153,6 +155,7 @@ fun SpendAnalysisScaffoldRenderer( } }, screenName = MMScreen.SPEND_ANALYSIS.screen, + getEventTracker = getEventTracker, ) } } @@ -166,6 +169,7 @@ fun SpendAnalysisScaffoldRenderer( onBarGraphElementClicked = { selectedMonth -> onBarGraphElementClicked(selectedMonth) }, + getEventTracker = getEventTracker, ) } } @@ -175,13 +179,14 @@ fun SpendAnalysisScaffoldRenderer( item { SpendCategorizationSection( screenName = MMScreen.SPEND_ANALYSIS.screen, - isAddAccountSelected = false, + addAccountState = AddAccountState.None, spendCategorizationState = spendCategorizationState, spendCategorizationAction = { action -> if (action is SpendCategorizationAction.SelectCategory) { onCategoryClick(action.categoryId) } }, + getEventTracker = getEventTracker, ) } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/ui/SpendAnalysisScreen.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/ui/SpendAnalysisScreen.kt index b29de71cad..d29d2b60aa 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/ui/SpendAnalysisScreen.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/ui/SpendAnalysisScreen.kt @@ -30,7 +30,6 @@ import com.navi.common.constants.MONTH import com.navi.common.constants.YEAR import com.navi.common.navigation.NavigationAction import com.navi.common.utils.Constants.WEB_URL -import com.navi.moneymanager.common.analytics.SpendAnalysisEventTrackerImpl import com.navi.moneymanager.common.composable.bankselectionbottomsheet.BankSelectionBottomSheetContent import com.navi.moneymanager.common.dataprovider.data.datastore.DataStoreInfoProvider import com.navi.moneymanager.common.model.SelectedMonth @@ -88,13 +87,13 @@ fun SpendAnalysisScreen( selectedMonth.value.month ?: getDayMonthAndYearFromTimestamp(System.currentTimeMillis()).second ) - SpendAnalysisEventTrackerImpl.onSpendAnalysisScreenLanded( + viewModel.spendAnalysisEventTracker.onSpendAnalysisScreenLanded( monthIndex = selectedMonthIndex, numberOfBanks = state.screenData?.totalSpendSection?.selectedBankReferenceIds?.size.orZero(), ) }, - onScreenExit = { SpendAnalysisEventTrackerImpl.onSpendAnalysisScreenExit() }, + onScreenExit = { viewModel.spendAnalysisEventTracker.onSpendAnalysisScreenExit() }, ) ScreenInit(screenName = MMScreen.SPEND_ANALYSIS.screen, activity = activity) @@ -106,12 +105,14 @@ fun SpendAnalysisScreen( is SpendAnalysisScreenUiEffect.Navigation.Back -> { navigateToPreviousScreen(activity) } + is SpendAnalysisScreenUiEffect.Navigation.NavigateToCta -> { activity.mmNavigator.navigateTo( activity = activity, ctaData = effect.ctaData, ) } + SpendAnalysisScreenUiEffect.Navigation.Help -> { viewModel.sendEvent( SpendAnalysisScreenUiEvent.ShowBottomSheet( @@ -119,6 +120,7 @@ fun SpendAnalysisScreen( ) ) } + is SpendAnalysisScreenUiEffect.Navigation.TransactionHistory -> { activity.mmNavigator.navigateTo( activity = activity, @@ -130,6 +132,7 @@ fun SpendAnalysisScreen( ), ) } + is SpendAnalysisScreenUiEffect.Navigation.CategoryDetailsScreen -> { activity.mmNavigator.navigateTo( activity = activity, @@ -138,12 +141,14 @@ fun SpendAnalysisScreen( screenData = effect.data, ) } + is SpendAnalysisScreenUiEffect.UpdateSelectedBankReferenceIds -> { viewModel.updateScreenParams( selectedMonth = selectedMonth.value, selectedBankReferenceIds = effect.selectedBankReferenceIds, ) } + is SpendAnalysisScreenUiEffect.Navigation.SpendGoalScreen -> { activity.mmNavigator.navigateTo( activity = activity, @@ -152,9 +157,11 @@ fun SpendAnalysisScreen( screenData = effect.data, ) } + SpendAnalysisScreenUiEffect.FetchConsentUrl -> { viewModel.fetchConsentUrl() } + is SpendAnalysisScreenUiEffect.Navigation.TransactionDetails -> { activity.mmNavigator.navigateTo( activity = activity, @@ -164,6 +171,7 @@ fun SpendAnalysisScreen( navigationAction = NavigationAction.Default, ) } + is SpendAnalysisScreenUiEffect.OpenCategoryBottomSheet -> { viewModel.sendEvent( SpendAnalysisScreenUiEvent.UpdateTransactionData(effect.transaction) @@ -188,16 +196,16 @@ fun SpendAnalysisScreen( modifier = Modifier.fillMaxSize(), spendAnalysisScreenUiState = state, onMonthChangeClick = { - SpendAnalysisEventTrackerImpl.onSpendAnalysisScreenMonthSelectionRequested() + viewModel.spendAnalysisEventTracker.onSpendAnalysisScreenMonthSelectionRequested() viewModel.handleMonthSelectBottomSheet(it) }, onBankSelectionRequest = { - SpendAnalysisEventTrackerImpl.onSpendAnalysisScreenBankSelectionRequested() + viewModel.spendAnalysisEventTracker.onSpendAnalysisScreenBankSelectionRequested() viewModel.handleBankSelectBottomSheet(it) }, onCategoryClick = { categoryId -> if (categoryId == Constants.OTHERS_CATEGORY_ID) { - SpendAnalysisEventTrackerImpl.onSpendAnalysisScreenOtherCategoriesClicked() + viewModel.spendAnalysisEventTracker.onSpendAnalysisScreenOtherCategoriesClicked() viewModel.handleOtherCategoriesBottomSheet(selectedMonth.value) } else { viewModel.setEffect { @@ -214,11 +222,11 @@ fun SpendAnalysisScreen( } }, onAverageInfoClick = { averageValue -> - SpendAnalysisEventTrackerImpl.onSpendAnalysisScreenGraphAverageInfoClicked() + viewModel.spendAnalysisEventTracker.onSpendAnalysisScreenGraphAverageInfoClicked() viewModel.handleAverageInfoBottomSheet(averageValue) }, onViewAllTransactionsClick = { - SpendAnalysisEventTrackerImpl.onSpendAnalysisScreenViewTransactionHistoryClicked() + viewModel.spendAnalysisEventTracker.onSpendAnalysisScreenViewTransactionHistoryClicked() viewModel.setEffect { SpendAnalysisScreenUiEffect.Navigation.TransactionHistory } }, onBarGraphElementClicked = { selectedMonth.value = it }, @@ -226,6 +234,7 @@ fun SpendAnalysisScreen( selectedMonth = selectedMonth.value, onEvent = { viewModel.sendEvent(it) }, onEffect = { viewModel.setEffect { it } }, + getEventTracker = { viewModel.spendAnalysisEventTracker }, ) MMBottomSheet( @@ -239,6 +248,7 @@ fun SpendAnalysisScreen( onEvent = { viewModel.sendEvent(it) }, onEffect = { viewModel.setEffect { it } }, selectedMonth = selectedMonth, + getViewModel = { viewModel }, ) } @@ -270,6 +280,7 @@ private fun SpendAnalysisBottomSheetContentHandler( dataStoreInfoProvider: DataStoreInfoProvider, onEffect: (SpendAnalysisScreenUiEffect) -> Unit, selectedMonth: MutableState, + getViewModel: () -> SpendAnalysisVM, ) { if (bottomSheet.sheetData == null) return val onDismissAction = bottomSheet.createDismissAction(onEvent) @@ -279,9 +290,9 @@ private fun SpendAnalysisBottomSheetContentHandler( bottomSheetData = bottomSheet.data, onBackIconClicked = { onDismissAction() }, onBankSelectionApplied = { selectedBankReferenceIds -> - SpendAnalysisEventTrackerImpl.onSpendAnalysisScreenBankSelectionApplied( - selectedBankReferenceIds.size - ) + getViewModel() + .spendAnalysisEventTracker + .onSpendAnalysisScreenBankSelectionApplied(selectedBankReferenceIds.size) onEffect( SpendAnalysisScreenUiEffect.UpdateSelectedBankReferenceIds( selectedBankReferenceIds @@ -291,6 +302,7 @@ private fun SpendAnalysisBottomSheetContentHandler( }, ) } + is SpendAnalysisScreenBottomSheets.MonthSelection -> { MonthSelectionBottomSheetUI( screenName = MMScreen.SPEND_ANALYSIS.screen, @@ -301,8 +313,10 @@ private fun SpendAnalysisBottomSheetContentHandler( val month = monthList.indexOf(monthYear.first) selectedMonth.value = SelectedMonth(month, monthYear.second) }, + getEventTracker = { getViewModel().spendAnalysisEventTracker }, ) } + is SpendAnalysisScreenBottomSheets.OtherCategories -> { OtherCategoriesBottomSheetContent( bottomSheetData = bottomSheet.data!!, @@ -328,37 +342,45 @@ private fun SpendAnalysisBottomSheetContentHandler( ) }, onConfirm = { - SpendAnalysisEventTrackerImpl + getViewModel() + .spendAnalysisEventTracker .onSpendAnalysisScreenOtherCategoriesBottomSheetAcknowledged() onDismissAction() }, trackCategoryItemClick = { categoryRank, categoryType -> - SpendAnalysisEventTrackerImpl + getViewModel() + .spendAnalysisEventTracker .onSpendAnalysisScreenOtherCategoriesBottomSheetCategoryClicked( categoryRank, categoryType, ) }, trackCategoryItemView = { categoryRank, categoryType -> - SpendAnalysisEventTrackerImpl + getViewModel() + .spendAnalysisEventTracker .onSpendAnalysisScreenOtherCategoriesBottomSheetCategoryViewed( categoryRank, categoryType, ) }, + getEventTracker = { getViewModel().spendAnalysisEventTracker }, ) } + is SpendAnalysisScreenBottomSheets.AverageInfo -> { AverageInfoBottomSheetContent( screenName = MMScreen.SPEND_ANALYSIS.screen, averageValue = bottomSheet.averageValue.orEmpty(), onDismiss = { - SpendAnalysisEventTrackerImpl + getViewModel() + .spendAnalysisEventTracker .onSpendAnalysisScreenAverageInfoBottomSheetAcknowledged() onDismissAction() }, + getEventTracker = { getViewModel().spendAnalysisEventTracker }, ) } + is SpendAnalysisScreenBottomSheets.PastMonthDataLoading -> { LaunchedEffect(Unit) { dataStoreInfoProvider.getBooleanData(key = IS_TOTAL_SYNC_COMPLETED).collect { @@ -372,15 +394,18 @@ private fun SpendAnalysisBottomSheetContentHandler( data = bottomSheet.data!!, onDismiss = { onDismissAction() }, trackBottomSheetAppears = { - SpendAnalysisEventTrackerImpl + getViewModel() + .spendAnalysisEventTracker .onSpendAnalysisPastMonthDataLoadingBottomSheetAppeared() }, trackBottomSheetDisappears = { - SpendAnalysisEventTrackerImpl + getViewModel() + .spendAnalysisEventTracker .onSpendAnalysisPastMonthDataLoadingBottomSheetDisappeared() }, ) } + is SpendAnalysisScreenBottomSheets.HelpBottomSheet -> { HelpBottomSheetContent( screenName = MMScreen.SPEND_ANALYSIS.screen, @@ -400,8 +425,10 @@ private fun SpendAnalysisBottomSheetContentHandler( ) }, onDismiss = onDismissAction, + getEventTracker = { getViewModel().spendAnalysisEventTracker }, ) } + is SpendAnalysisScreenBottomSheets.DailySpendBottomSheet -> { DailySpendBottomSheetContent( bottomSheet.data!!, @@ -418,14 +445,17 @@ private fun SpendAnalysisBottomSheetContentHandler( onEffect(SpendAnalysisScreenUiEffect.OpenCategoryBottomSheet(it)) }, onViewAllTransactionsClick = { - SpendAnalysisEventTrackerImpl + getViewModel() + .spendAnalysisEventTracker .onSpendAnalysisDailySpendBottomSheetViewTransactionsClicked() onDismissAction() onEffect(SpendAnalysisScreenUiEffect.Navigation.TransactionHistory) }, onDismiss = { onDismissAction() }, + getViewModel = getViewModel, ) } + is SpendAnalysisScreenBottomSheets.ProgressBarBottomSheet -> { ProgressBarBottomSheet( data = bottomSheet.data!!, @@ -442,8 +472,10 @@ private fun SpendAnalysisBottomSheetContentHandler( ) ) }, + getEventTracker = { getViewModel().spendAnalysisEventTracker }, ) } + SpendAnalysisScreenBottomSheets.NoBottomSheet -> {} } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/ui/TotalSpendSection.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/ui/TotalSpendSection.kt index 5b269b4282..d3edd8df6c 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/ui/TotalSpendSection.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/ui/TotalSpendSection.kt @@ -24,7 +24,6 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -32,13 +31,14 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.navi.base.utils.toAbbreviatedINRFormat -import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper import com.navi.design.font.FontWeightEnum import com.navi.elex.theme.elexColors +import com.navi.moneymanager.common.analytics.SpendAnalysisEventTrackerImpl import com.navi.moneymanager.common.illustration.model.IllustrationType import com.navi.moneymanager.common.illustration.model.ImageProperties import com.navi.moneymanager.common.illustration.ui.Illustration import com.navi.moneymanager.common.model.SpendCategorizationAction +import com.navi.moneymanager.common.model.SpendGoalState import com.navi.moneymanager.common.ui.composable.base.MMText import com.navi.moneymanager.common.ui.composable.sectionHeaders.SectionHeader import com.navi.moneymanager.common.utils.Constants.SLASH_WITH_ADJACENT_SPACES @@ -53,10 +53,8 @@ fun TotalSpendSectionUI( onBankSelectionRequest: (Set) -> Unit, spendCategorizationAction: (SpendCategorizationAction) -> Unit, screenName: String, + getEventTracker: () -> SpendAnalysisEventTrackerImpl, ) { - val isGoalsEnabled = remember { - FirebaseRemoteConfigHelper.getBoolean(FirebaseRemoteConfigHelper.MM_SPEND_GOALS_ENABLED) - } Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { Column( modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), @@ -170,38 +168,48 @@ fun TotalSpendSectionUI( fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD, lineHeight = 24.sp, ) - if (isGoalsEnabled) { - data.spendGoal?.let { - MMText( - modifier = Modifier.padding(top = 4.dp), - text = - SLASH_WITH_ADJACENT_SPACES + - data.spendGoal.toAbbreviatedINRFormat(), - color = elexColors.base.text.gray.dim, - fontSize = 14.sp, - fontWeight = FontWeightEnum.NAVI_BODY_REGULAR, - ) + when (data.spendGoalState) { + is SpendGoalState.Loaded -> { + data.spendGoalState.spendGoal?.let { + MMText( + modifier = Modifier.padding(top = 4.dp), + text = + SLASH_WITH_ADJACENT_SPACES + + data.spendGoalState.spendGoal.toAbbreviatedINRFormat(), + color = elexColors.base.text.gray.dim, + fontSize = 14.sp, + fontWeight = FontWeightEnum.NAVI_BODY_REGULAR, + ) + } } + else -> {} } } } - if (isGoalsEnabled) { - Row( - modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp, top = 8.dp) - ) { - TotalSpendsSpendGoalSection( - data = data, - onProgressBarClick = { - spendCategorizationAction( - SpendCategorizationAction.ShowProgressBarBottomSheet(it) - ) - }, - onSetGoalCardClick = { - spendCategorizationAction(SpendCategorizationAction.SetSpendGoal) - }, - screenName = screenName, - ) + when (data.spendGoalState) { + is SpendGoalState.Loaded -> { + Row( + modifier = + Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp, top = 8.dp) + ) { + TotalSpendsSpendGoalSection( + selectedMonth = data.selectedMonth, + totalSpendAmount = data.amount, + data = data.spendGoalState, + onProgressBarClick = { + spendCategorizationAction( + SpendCategorizationAction.ShowProgressBarBottomSheet(it) + ) + }, + onSetGoalCardClick = { + spendCategorizationAction(SpendCategorizationAction.SetSpendGoal) + }, + screenName = screenName, + getEventTracker = getEventTracker, + ) + } } + else -> {} } Spacer(Modifier.height(32.dp)) diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/viewmodel/SpendAnalysisVM.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/viewmodel/SpendAnalysisVM.kt index 20f4d173d9..cdd7ae7b5b 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/viewmodel/SpendAnalysisVM.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/viewmodel/SpendAnalysisVM.kt @@ -10,14 +10,19 @@ package com.navi.moneymanager.postonboard.spendanalysis.viewmodel import androidx.lifecycle.viewModelScope import com.navi.moneymanager.base.viewmodel.MMBaseViewModel import com.navi.moneymanager.common.analytics.DataProviderEventTrackerImpl +import com.navi.moneymanager.common.analytics.SpendAnalysisEventTrackerImpl +import com.navi.moneymanager.common.analytics.TransactionHistoryEventTrackerImpl +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper import com.navi.moneymanager.common.dataprovider.data.datastore.DataStoreInfoProvider import com.navi.moneymanager.common.model.FilterAttribute import com.navi.moneymanager.common.model.SelectedMonth import com.navi.moneymanager.common.model.SpendAnalysisScreenInputParams import com.navi.moneymanager.common.model.bottomSheet.SpendAnalysisScreenBottomSheets +import com.navi.moneymanager.common.model.sectionHeader.MMJourneySource import com.navi.moneymanager.common.navigation.utils.MMScreen import com.navi.moneymanager.common.network.di.RoomDataStoreInfoProvider import com.navi.moneymanager.common.utils.Constants.IS_TOTAL_SYNC_COMPLETED +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE import com.navi.moneymanager.common.utils.asList import com.navi.moneymanager.common.utils.getTransactionFilterMonthWithYear import com.navi.moneymanager.postonboard.help.model.HelpBottomSheetState @@ -52,6 +57,7 @@ constructor( @RoomDataStoreInfoProvider val dbDataStoreProvider: DataStoreInfoProvider, private val repository: SpendAnalysisRepository, private val manageConsentUseCase: ManageConsentUseCase, + val mmConfigResponseHelper: MMConfigResponseHelper, ) : MMBaseViewModel< SpendAnalysisScreenUiState, @@ -65,13 +71,31 @@ constructor( private var selectedBanksPillData = listOf() private val screenParams = MutableStateFlow(null) + private val dataProviderEventTracker by lazy { + DataProviderEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + + val spendAnalysisEventTracker by lazy { + SpendAnalysisEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + + val transactionHistoryEventTracker by lazy { + TransactionHistoryEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + @OptIn(ExperimentalCoroutinesApi::class) private val spendAnalysisScreenData = screenParams .filterNotNull() .distinctUntilChanged() .flatMapLatest { params -> - DataProviderEventTrackerImpl.spendAnalysisVmCollect( + dataProviderEventTracker.spendAnalysisVmCollect( month = params.month.toString(), year = params.year.toString(), selectedBanksListSize = params.selectedBankReferenceIds?.size.toString(), @@ -109,7 +133,7 @@ constructor( selectedBankReferenceIds: Set? = null, ) { viewModelScope.safeLaunch(Dispatchers.IO) { - DataProviderEventTrackerImpl.spendAnalysisVmUpdate( + dataProviderEventTracker.spendAnalysisVmUpdate( month = selectedMonth.month.toString(), year = selectedMonth.year.toString(), selectedBanksListSize = selectedBankReferenceIds?.size.toString(), @@ -128,7 +152,9 @@ constructor( viewModelScope.safeLaunch(Dispatchers.IO) { val isTotalSyncCompleted = dbDataStoreProvider.getBooleanData(key = IS_TOTAL_SYNC_COMPLETED).first() - if (isTotalSyncCompleted) { + val isUpiSpendAnalysis = + mmConfigResponseHelper.journeySource == MMJourneySource.NAVI_UPI.name + if (isUpiSpendAnalysis || isTotalSyncCompleted) { val data = repository.getMonthSelectionBottomSheetData(displayMonthText) sendEvent( SpendAnalysisScreenUiEvent.ShowBottomSheet( @@ -178,7 +204,9 @@ constructor( viewModelScope.safeLaunch(Dispatchers.IO) { val isTotalSyncCompleted = dbDataStoreProvider.getBooleanData(key = IS_TOTAL_SYNC_COMPLETED).first() - if (isTotalSyncCompleted) { + val isUpiSpendAnalysis = + mmConfigResponseHelper.journeySource == MMJourneySource.NAVI_UPI.name + if (isTotalSyncCompleted || isUpiSpendAnalysis) { sendEvent( SpendAnalysisScreenUiEvent.ShowBottomSheet( type = SpendAnalysisScreenBottomSheets.AverageInfo(averageValue) diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/helper/SpendGoalHelper.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/helper/SpendGoalHelper.kt index 487748be41..d335adbc71 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/helper/SpendGoalHelper.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/helper/SpendGoalHelper.kt @@ -33,6 +33,7 @@ class SpendGoalHelper @Inject constructor() { repository: SpendGoalRepository, sendEvent: (SpendGoalScreenUiEvent) -> Unit, setEffect: (SpendGoalScreenUiEffect) -> Unit, + getEventTracker: () -> SpendGoalEventTrackerImpl, ) { sendEvent(goalLoadingState(isBottomSheet)) @@ -44,6 +45,7 @@ class SpendGoalHelper @Inject constructor() { isBottomSheet, sendEvent, setEffect, + getEventTracker, ) } @@ -55,6 +57,7 @@ class SpendGoalHelper @Inject constructor() { repository: SpendGoalRepository, sendEvent: (SpendGoalScreenUiEvent) -> Unit, setEffect: (SpendGoalScreenUiEffect) -> Unit, + getEventTracker: () -> SpendGoalEventTrackerImpl, ) { sendEvent(goalLoadingState(isBottomSheet)) @@ -66,6 +69,7 @@ class SpendGoalHelper @Inject constructor() { isBottomSheet, sendEvent, setEffect, + getEventTracker, ) } @@ -74,6 +78,7 @@ class SpendGoalHelper @Inject constructor() { repository: SpendGoalRepository, sendEvent: (SpendGoalScreenUiEvent) -> Unit, setEffect: (SpendGoalScreenUiEffect) -> Unit, + getEventTracker: () -> SpendGoalEventTrackerImpl, ) { sendEvent( SpendGoalScreenUiEvent.UpdateDeleteGoalBottomSheetState( @@ -84,7 +89,7 @@ class SpendGoalHelper @Inject constructor() { sendEvent(SpendGoalScreenUiEvent.DismissBottomSheet()) if (response.isSuccessWithData()) { setEffect(SpendGoalScreenUiEffect.Navigation.NavigateBackToPreviousScreen) - SpendGoalEventTrackerImpl.onSpendGoalDeleteGoalSuccess(goalId.toString()) + getEventTracker().onSpendGoalDeleteGoalSuccess(goalId.toString()) } else { setEffect(SpendGoalScreenUiEffect.ShowErrorToast) sendEvent( @@ -92,10 +97,11 @@ class SpendGoalHelper @Inject constructor() { DeleteGoalBottomSheetState.Initial(goalId) ) ) - SpendGoalEventTrackerImpl.onSpendGoalDeleteGoalError( - errorMessage = response.error?.message ?: SOMETHING_WENT_WRONG, - goalId = goalId.toString(), - ) + getEventTracker() + .onSpendGoalDeleteGoalError( + errorMessage = response.error?.message ?: SOMETHING_WENT_WRONG, + goalId = goalId.toString(), + ) } } @@ -106,6 +112,7 @@ class SpendGoalHelper @Inject constructor() { isBottomSheet: Boolean, sendEvent: (SpendGoalScreenUiEvent) -> Unit, setEffect: (SpendGoalScreenUiEffect) -> Unit, + getEventTracker: () -> SpendGoalEventTrackerImpl, ) { sendEvent(SpendGoalScreenUiEvent.DismissBottomSheet()) if (response.isSuccessWithData()) { @@ -120,7 +127,7 @@ class SpendGoalHelper @Inject constructor() { ) ) ) - SpendGoalEventTrackerImpl.onSpendGoalSetGoalSuccess(goalAmount.toString()) + getEventTracker().onSpendGoalSetGoalSuccess(goalAmount.toString()) } else { if (isBottomSheet) { sendEvent( @@ -145,10 +152,11 @@ class SpendGoalHelper @Inject constructor() { ) ) } - SpendGoalEventTrackerImpl.onSpendGoalSetGoalError( - errorMessage = response.error?.message ?: SOMETHING_WENT_WRONG, - goalAmount = goalAmount.toString(), - ) + getEventTracker() + .onSpendGoalSetGoalError( + errorMessage = response.error?.message ?: SOMETHING_WENT_WRONG, + goalAmount = goalAmount.toString(), + ) } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/SpendGoalBottomSheetContentHandler.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/SpendGoalBottomSheetContentHandler.kt index 3fe5155a43..2d8d013f46 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/SpendGoalBottomSheetContentHandler.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/SpendGoalBottomSheetContentHandler.kt @@ -36,17 +36,16 @@ fun SpendGoalBottomSheetContentHandler( onEvent: (SpendGoalScreenUiEvent) -> Unit, getViewModel: () -> SpendGoalViewModel, onEffect: (SpendGoalScreenUiEffect) -> Unit, + getEventTracker: () -> SpendGoalEventTrackerImpl, ) { if (bottomSheet.sheetData == null) return val onDismissAction = bottomSheet.createDismissAction(onEvent) LaunchedEffect(Unit) { - SpendGoalEventTrackerImpl.onSpendGoalBottomSheetAppeared(bottomSheet.javaClass.simpleName) + getEventTracker().onSpendGoalBottomSheetAppeared(bottomSheet.javaClass.simpleName) } DisposableEffect(Unit) { onDispose { - SpendGoalEventTrackerImpl.onSpendGoalBottomSheetDisappeared( - bottomSheet.javaClass.simpleName - ) + getEventTracker().onSpendGoalBottomSheetDisappeared(bottomSheet.javaClass.simpleName) } } when (bottomSheet) { @@ -55,6 +54,7 @@ fun SpendGoalBottomSheetContentHandler( screenName = MMScreen.SPEND_ANALYSIS.screen, averageValue = bottomSheet.averageValue.orEmpty(), onDismiss = { onDismissAction() }, + getEventTracker = getEventTracker, ) } @@ -85,6 +85,7 @@ fun SpendGoalBottomSheetContentHandler( ) }, onDismiss = onDismissAction, + getEventTracker = getEventTracker, ) } @@ -99,6 +100,7 @@ fun SpendGoalBottomSheetContentHandler( onDismissAction() }, screenName = MMScreen.SPEND_GOAL_SCREEN.name, + getEventTracker = getEventTracker, ) } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/SpendGoalScaffoldRenderer.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/SpendGoalScaffoldRenderer.kt index 33d27da219..d5772b919d 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/SpendGoalScaffoldRenderer.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/SpendGoalScaffoldRenderer.kt @@ -176,6 +176,9 @@ fun SpendGoalScaffoldRenderer( barGraphData = barGraphData, clearFocus = { focusManager.clearFocus() }, onAverageInfoClick = onAverageInfoClick, + getEventTracker = { + getViewModel().spendGoalEventTracker + }, ) } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/SpendGoalScreen.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/SpendGoalScreen.kt index aca8b629b8..9250390351 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/SpendGoalScreen.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/SpendGoalScreen.kt @@ -22,7 +22,6 @@ import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.navi.moneymanager.R -import com.navi.moneymanager.common.analytics.SpendGoalEventTrackerImpl import com.navi.moneymanager.common.illustration.model.IllustrationSource import com.navi.moneymanager.common.illustration.model.IllustrationType import com.navi.moneymanager.common.model.bottomSheet.SpendGoalScreenBottomSheets @@ -59,8 +58,8 @@ fun SpendGoalScreen( var showKeyboard by remember { mutableStateOf(false) } MMScreenEventLogger( - onScreenLand = { SpendGoalEventTrackerImpl.onSpendGoalScreenLanded() }, - onScreenExit = { SpendGoalEventTrackerImpl.onSpendGoalScreenExit() }, + onScreenLand = { viewModel.spendGoalEventTracker.onSpendGoalScreenLanded() }, + onScreenExit = { viewModel.spendGoalEventTracker.onSpendGoalScreenExit() }, ) ScreenInit(screenName = MMScreen.SPEND_GOAL_SCREEN.screen, activity = activity) @@ -134,6 +133,7 @@ fun SpendGoalScreen( onEvent = { viewModel.sendEvent(it) }, getViewModel = { viewModel }, onEffect = { viewModel.setEffect { it } }, + getEventTracker = { viewModel.spendGoalEventTracker }, ) } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/SpendGoalSections.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/SpendGoalSections.kt index 88b2207c97..dd3436fe38 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/SpendGoalSections.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/SpendGoalSections.kt @@ -35,34 +35,36 @@ import com.navi.common.utils.onClickWithDebounce import com.navi.design.font.FontWeightEnum import com.navi.elex.theme.elexColors import com.navi.moneymanager.R +import com.navi.moneymanager.common.analytics.SpendAnalysisEventTrackerImpl import com.navi.moneymanager.common.analytics.SpendGoalEventTracker -import com.navi.moneymanager.common.analytics.SpendGoalEventTrackerImpl import com.navi.moneymanager.common.illustration.model.IllustrationSource import com.navi.moneymanager.common.illustration.model.IllustrationType import com.navi.moneymanager.common.illustration.model.ImageProperties import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.CHEVRON_PURPLE_RIGHT_ICON import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.IMAGE_TRANSPARENT_PLACEHOLDER_MEDIUM import com.navi.moneymanager.common.illustration.ui.Illustration -import com.navi.moneymanager.common.model.TotalSpends +import com.navi.moneymanager.common.model.SpendGoalState import com.navi.moneymanager.common.ui.composable.MMTotalSpendProgressBar import com.navi.moneymanager.common.ui.composable.base.MMText import com.navi.moneymanager.common.ui.composable.goals.GoalNotSetCard import com.navi.moneymanager.common.ui.composable.goals.SpendGoalCard -import com.navi.moneymanager.postonboard.spendanalysis.model.TotalSpendSectionData import com.navi.moneymanager.preonboard.launcher.model.ProgressBarBottomSheetData import com.navi.uitron.utils.shapes.ToolTipShape @Composable fun TotalSpendsSpendGoalSection( - data: TotalSpendSectionData, + selectedMonth: String, + totalSpendAmount: String, + data: SpendGoalState.Loaded, screenName: String, onProgressBarClick: (ProgressBarBottomSheetData) -> Unit, onSetGoalCardClick: () -> Unit, + getEventTracker: () -> SpendAnalysisEventTrackerImpl, ) { val dateComponents = getDayMonthAndYearFromTimestamp(System.currentTimeMillis()) val isCurrentMonth = monthAbbreviations[dateComponents.second].plus(SPACE).plus(dateComponents.third) == - data.selectedMonth + selectedMonth when { isCurrentMonth && data.progress != null -> { Row( @@ -93,16 +95,18 @@ fun TotalSpendsSpendGoalSection( ) ) .onClickWithDebounce { - SpendGoalEventTrackerImpl.onSpendGoalEntryPointClicked( - screenName = screenName, - type = - SpendGoalEventTracker.SPEND_GOAL_PROGRESS_BAR_CURRENT_MONTH, - ) + getEventTracker() + .onSpendGoalEntryPointClicked( + screenName = screenName, + type = + SpendGoalEventTracker + .SPEND_GOAL_PROGRESS_BAR_CURRENT_MONTH, + ) onProgressBarClick( ProgressBarBottomSheetData( progress = data.progress, spendGoal = data.spendGoal, - totalSpend = data.amount, + totalSpend = totalSpendAmount, isGoalEditable = true, ) ) @@ -143,12 +147,13 @@ fun TotalSpendsSpendGoalSection( modifier = Modifier.clip(shape = RoundedCornerShape(bottomEnd = 8.dp)) .onClickWithDebounce { - SpendGoalEventTrackerImpl.onSpendGoalEntryPointClicked( - screenName = screenName, - type = - SpendGoalEventTracker - .SPEND_GOAL_PROGRESS_BAR_UPDATE_GOAL, - ) + getEventTracker() + .onSpendGoalEntryPointClicked( + screenName = screenName, + type = + SpendGoalEventTracker + .SPEND_GOAL_PROGRESS_BAR_UPDATE_GOAL, + ) onSetGoalCardClick() } .padding( @@ -219,15 +224,16 @@ fun TotalSpendsSpendGoalSection( ) ) .onClickWithDebounce { - SpendGoalEventTrackerImpl.onSpendGoalEntryPointClicked( - screenName = screenName, - type = SpendGoalEventTracker.SPEND_GOAL_PROGRESS_BAR, - ) + getEventTracker() + .onSpendGoalEntryPointClicked( + screenName = screenName, + type = SpendGoalEventTracker.SPEND_GOAL_PROGRESS_BAR, + ) onProgressBarClick.invoke( ProgressBarBottomSheetData( progress = data.progress, spendGoal = data.spendGoal, - totalSpend = data.amount, + totalSpend = totalSpendAmount, isGoalEditable = false, ) ) @@ -259,10 +265,11 @@ fun TotalSpendsSpendGoalSection( isCurrentMonth -> { SpendGoalCard(isToolTipShape = true) { - SpendGoalEventTrackerImpl.onSpendGoalEntryPointClicked( - screenName = screenName, - type = SpendGoalEventTracker.SPEND_GOAL_SET_GOAL_CARD, - ) + getEventTracker() + .onSpendGoalEntryPointClicked( + screenName = screenName, + type = SpendGoalEventTracker.SPEND_GOAL_SET_GOAL_CARD, + ) onSetGoalCardClick() } } @@ -271,17 +278,18 @@ fun TotalSpendsSpendGoalSection( GoalNotSetCard( isToolTipShape = true, onAction = { bottomSheetData -> - SpendGoalEventTrackerImpl.onSpendGoalEntryPointClicked( - screenName = screenName, - type = SpendGoalEventTracker.SPEND_GOAL_NO_GOAL_CARD, - ) + getEventTracker() + .onSpendGoalEntryPointClicked( + screenName = screenName, + type = SpendGoalEventTracker.SPEND_GOAL_NO_GOAL_CARD, + ) onProgressBarClick(bottomSheetData) }, data = ProgressBarBottomSheetData( progress = data.progress, spendGoal = data.spendGoal, - totalSpend = data.amount, + totalSpend = totalSpendAmount, isGoalEditable = false, ), ) @@ -291,11 +299,13 @@ fun TotalSpendsSpendGoalSection( @Composable fun CategoryTotalSpendsSpendGoalSection( - data: TotalSpends.Loaded, + totalSpendAmount: String, + data: SpendGoalState.Loaded, isCurrentMonth: Boolean, screenName: String, onProgressBarClick: ((ProgressBarBottomSheetData) -> Unit)? = null, onSetGoalCardClick: (() -> Unit)? = null, + goalEventTracker: () -> SpendAnalysisEventTrackerImpl, ) { when { data.progress != null -> { @@ -305,15 +315,16 @@ fun CategoryTotalSpendsSpendGoalSection( .fillMaxWidth() .clip(shape = RoundedCornerShape(8.dp)) .onClickWithDebounce { - SpendGoalEventTrackerImpl.onSpendGoalEntryPointClicked( - screenName = screenName, - type = SpendGoalEventTracker.SPEND_GOAL_PROGRESS_BAR, - ) + goalEventTracker() + .onSpendGoalEntryPointClicked( + screenName = screenName, + type = SpendGoalEventTracker.SPEND_GOAL_PROGRESS_BAR, + ) onProgressBarClick?.invoke( ProgressBarBottomSheetData( progress = data.progress, spendGoal = data.spendGoal, - totalSpend = data.subtitle, + totalSpend = totalSpendAmount, isGoalEditable = isCurrentMonth, ) ) @@ -359,10 +370,11 @@ fun CategoryTotalSpendsSpendGoalSection( Modifier.padding(bottom = 24.dp, start = 16.dp, end = 16.dp).fillMaxWidth() ) { SpendGoalCard { - SpendGoalEventTrackerImpl.onSpendGoalEntryPointClicked( - screenName = screenName, - type = SpendGoalEventTracker.SPEND_GOAL_SET_GOAL_CARD, - ) + goalEventTracker() + .onSpendGoalEntryPointClicked( + screenName = screenName, + type = SpendGoalEventTracker.SPEND_GOAL_SET_GOAL_CARD, + ) onSetGoalCardClick?.invoke() } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/bottomsheet/ProgressBarBottomSheet.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/bottomsheet/ProgressBarBottomSheet.kt index 7434180fea..2c1602c8e8 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/bottomsheet/ProgressBarBottomSheet.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/bottomsheet/ProgressBarBottomSheet.kt @@ -41,7 +41,7 @@ import com.navi.design.font.getFontWeight import com.navi.design.font.naviFontFamily import com.navi.elex.theme.elexColors import com.navi.moneymanager.R -import com.navi.moneymanager.common.analytics.SpendGoalEventTrackerImpl +import com.navi.moneymanager.common.analytics.ProgressBarBottomSheetEventTracker import com.navi.moneymanager.common.illustration.model.IllustrationSource import com.navi.moneymanager.common.illustration.model.IllustrationType import com.navi.moneymanager.common.illustration.model.ImageProperties @@ -57,15 +57,15 @@ fun ProgressBarBottomSheet( screenName: String, data: ProgressBarBottomSheetData, onModifyClick: () -> Unit, + getEventTracker: () -> ProgressBarBottomSheetEventTracker, ) { - LaunchedEffect(Unit) { - SpendGoalEventTrackerImpl.onSpendGoalProgressBottomSheetAppeared(screenName) - } + + LaunchedEffect(Unit) { getEventTracker().onProgressBarBottomSheetAppeared(screenName) } when (data.spendGoal.isNotNull()) { true -> GoalSetProgressBarBottomSheet( onDismiss = { - SpendGoalEventTrackerImpl.onSpendGoalProgressBottomSheetDisappeared(screenName) + getEventTracker().onProgressBarBottomSheetDisappeared(screenName) onDismiss() }, data = data, @@ -75,7 +75,7 @@ fun ProgressBarBottomSheet( false -> GoalNotSetProgressBarBottomSheet( onDismiss = { - SpendGoalEventTrackerImpl.onSpendGoalProgressBottomSheetDisappeared(screenName) + getEventTracker().onProgressBarBottomSheetDisappeared(screenName) onDismiss() } ) diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/composable/SpendGoalGraphSection.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/composable/SpendGoalGraphSection.kt index 60a298cd59..fb1e518a4c 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/composable/SpendGoalGraphSection.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/ui/composable/SpendGoalGraphSection.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.unit.sp import com.navi.design.font.FontWeightEnum import com.navi.elex.theme.elexColors import com.navi.moneymanager.R +import com.navi.moneymanager.common.analytics.SpendGoalEventTrackerImpl import com.navi.moneymanager.common.model.BarGraphData import com.navi.moneymanager.common.navigation.utils.MMScreen import com.navi.moneymanager.common.ui.composable.barGraph.MMBarGraph @@ -35,6 +36,7 @@ fun SpendGoalGraphSection( barGraphData: BarGraphData, clearFocus: () -> Unit, onAverageInfoClick: (String?) -> Unit, + getEventTracker: () -> SpendGoalEventTrackerImpl, ) { Column( modifier = Modifier.clickable(interactionSource = null, indication = null) { clearFocus() } @@ -75,6 +77,7 @@ fun SpendGoalGraphSection( }, onBarGraphElementClicked = {}, onBarGraphClicked = { clearFocus() }, + getEventTracker = getEventTracker, ) } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/viewmodel/SpendGoalViewModel.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/viewmodel/SpendGoalViewModel.kt index e4bc670f32..6193306d38 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/viewmodel/SpendGoalViewModel.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendgoal/viewmodel/SpendGoalViewModel.kt @@ -11,11 +11,14 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.lifecycle.viewModelScope import com.navi.base.utils.EMPTY import com.navi.moneymanager.base.viewmodel.MMBaseViewModel +import com.navi.moneymanager.common.analytics.SpendGoalEventTrackerImpl +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper import com.navi.moneymanager.common.dataprovider.data.datastore.DataStoreInfoProvider import com.navi.moneymanager.common.model.bottomSheet.SpendGoalScreenBottomSheets import com.navi.moneymanager.common.navigation.utils.MMScreen import com.navi.moneymanager.common.network.di.RoomDataStoreInfoProvider import com.navi.moneymanager.common.utils.Constants.IS_TOTAL_SYNC_COMPLETED +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE import com.navi.moneymanager.common.utils.formattedAmountToValue import com.navi.moneymanager.postonboard.help.model.HelpBottomSheetState import com.navi.moneymanager.postonboard.help.usecase.ManageConsentUseCase @@ -42,11 +45,17 @@ constructor( private val manageConsentUseCase: ManageConsentUseCase, private val handleSpendGoalScreenDataUseCase: HandleSpendGoalScreenDataUseCase, private val spendGoalHelper: SpendGoalHelper, + val mmConfigResponseHelper: MMConfigResponseHelper, ) : MMBaseViewModel( initialState = SpendGoalScreenUiState.initialState, reducer = SpendGoalScreenReducer(), ) { + val spendGoalEventTracker by lazy { + SpendGoalEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } init { handleSpendGoalScreenData() @@ -146,6 +155,7 @@ constructor( repository = repository, sendEvent = { event -> sendEvent(event) }, setEffect = { effect -> setEffect { effect } }, + getEventTracker = { spendGoalEventTracker }, ) } } @@ -160,6 +170,7 @@ constructor( repository = repository, sendEvent = { event -> sendEvent(event) }, setEffect = { effect -> setEffect { effect } }, + getEventTracker = { spendGoalEventTracker }, ) } } @@ -171,6 +182,7 @@ constructor( repository = repository, sendEvent = { event -> sendEvent(event) }, setEffect = { effect -> setEffect { effect } }, + getEventTracker = { spendGoalEventTracker }, ) } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/model/TransactionDetailsScreenData.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/model/TransactionDetailsScreenData.kt index 4d6672ec42..ccc474a180 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/model/TransactionDetailsScreenData.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/model/TransactionDetailsScreenData.kt @@ -31,8 +31,9 @@ data class CategoryInfo( val categoryIcon: IllustrationSource, val categoryTransactionData: CategoryBottomSheetTransactionData, val categoryName: String, - val categoryActionIcon: IllustrationSource, + val categoryActionIcon: IllustrationSource? = null, val categoryId: String, + val isClickable: Boolean = true, ) data class TransactionSummary( diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/repository/TransactionDetailsScreenRepository.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/repository/TransactionDetailsScreenRepository.kt index e93fcabe9d..fc9bab7381 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/repository/TransactionDetailsScreenRepository.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/repository/TransactionDetailsScreenRepository.kt @@ -7,12 +7,32 @@ package com.navi.moneymanager.postonboard.transactiondetails.repository +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper import com.navi.moneymanager.common.dataprovider.domain.TransactionDataProvider +import com.navi.moneymanager.common.dataprovider.domain.upi.UpiSpendTransactionDataProvider +import com.navi.moneymanager.common.model.sectionHeader.MMJourneySource +import com.navi.moneymanager.postonboard.transactiondetails.model.TransactionDetailsScreenData import javax.inject.Inject +import kotlinx.coroutines.flow.Flow class TransactionDetailsScreenRepository @Inject -constructor(private val transactionDataProvider: TransactionDataProvider) { - suspend fun getTransactionDetailScreenData(transactionId: String) = - transactionDataProvider.getTransactionDetailScreenData(transactionId) +constructor( + private val configHelper: MMConfigResponseHelper, + private val transactionDataProvider: TransactionDataProvider, + private val upiSpendTransactionDataProvider: UpiSpendTransactionDataProvider, +) { + suspend fun getTransactionDetailScreenData( + transactionId: String + ): Flow { + return when (configHelper.journeySource) { + MMJourneySource.NAVI_UPI.name -> { + upiSpendTransactionDataProvider.getTransactionDetailScreenData(transactionId) + } + + else -> { + transactionDataProvider.getTransactionDetailScreenData(transactionId) + } + } + } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/ui/TransactionCategorySection.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/ui/TransactionCategorySection.kt index 19cb9cb937..4020b02d53 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/ui/TransactionCategorySection.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/ui/TransactionCategorySection.kt @@ -48,7 +48,10 @@ import com.navi.moneymanager.postonboard.monthlysummary.ui.bottomsheet.CustomToa import com.navi.moneymanager.postonboard.transactiondetails.model.CategoryInfo @Composable -fun TransactionCategorySection(categoryInfo: CategoryInfo) { +fun TransactionCategorySection( + categoryInfo: CategoryInfo, + getEventTracker: () -> TransactionDetailsEventTrackerImpl, +) { val context = LocalContext.current val showBottomSheet = remember { mutableStateOf(false) } @@ -85,12 +88,16 @@ fun TransactionCategorySection(categoryInfo: CategoryInfo) { MMCard( modifier = Modifier.padding(horizontal = 16.dp).fillMaxWidth(), - onClick = { - TransactionDetailsEventTrackerImpl.onTransactionDetailsTransactionCategoryClicked( - category = categoryInfo.categoryId - ) - showBottomSheet.value = true - }, + onClick = + if (categoryInfo.isClickable) { + { + getEventTracker() + .onTransactionDetailsTransactionCategoryClicked( + category = categoryInfo.categoryId + ) + showBottomSheet.value = true + } + } else null, elevation = 0.dp, borderStroke = BorderStroke(width = 1.dp, color = elexColors.base.border.gray.standard), backgroundColor = @@ -127,19 +134,22 @@ fun TransactionCategorySection(categoryInfo: CategoryInfo) { ) Spacer(modifier = Modifier.weight(1f)) - - Illustration( - modifier = Modifier.size(16.dp), - illustrationType = - IllustrationType.Image( - categoryInfo.categoryActionIcon, - properties = - ImageProperties( - colorFilter = - ColorFilter.tint(elexColors.interactive.icon.primary.standard) - ), - ), - ) + categoryInfo.categoryActionIcon?.let { + Illustration( + modifier = Modifier.size(16.dp), + illustrationType = + IllustrationType.Image( + categoryInfo.categoryActionIcon, + properties = + ImageProperties( + colorFilter = + ColorFilter.tint( + elexColors.interactive.icon.primary.standard + ) + ), + ), + ) + } } } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/ui/TransactionDetailsScaffoldRenderer.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/ui/TransactionDetailsScaffoldRenderer.kt index bd7c8fc0dd..69485f2d71 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/ui/TransactionDetailsScaffoldRenderer.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/ui/TransactionDetailsScaffoldRenderer.kt @@ -20,7 +20,6 @@ import androidx.compose.ui.Modifier import com.navi.base.utils.EMPTY import com.navi.common.R as commonR import com.navi.elex.theme.elexColors -import com.navi.moneymanager.common.analytics.TransactionDetailsEventTrackerImpl import com.navi.moneymanager.common.ui.composable.MMTopBar import com.navi.moneymanager.postonboard.transactiondetails.model.TransactionDetailsScreenData import com.navi.moneymanager.postonboard.transactiondetails.model.TransactionDetailsScreenUiEffect @@ -44,7 +43,7 @@ fun TransactionDetailsScaffoldRenderer( navigationIcon = commonR.drawable.navi_common_ic_arrow_left_white_v2, actionIconText = data.topNavBar.actionLabel, onActionClick = { - TransactionDetailsEventTrackerImpl.onTransactionDetailsHelpClicked() + getViewModel().transactionHistoryEventTracker.onTransactionDetailsHelpClicked() getViewModel().setEffect { TransactionDetailsScreenUiEffect.Navigation.Help } }, titleColor = elexColors.base.text.staticWhite.standard, @@ -67,7 +66,10 @@ fun TransactionDetailsScaffoldRenderer( .background(color = elexColors.base.background.gray.intense), horizontalAlignment = Alignment.CenterHorizontally, ) { - TransactionCategorySection(data.categoryInfo) + TransactionCategorySection( + data.categoryInfo, + getEventTracker = { getViewModel().transactionHistoryEventTracker }, + ) TransactionSummarySection(data.transactionSummary) } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/ui/TransactionDetailsScreen.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/ui/TransactionDetailsScreen.kt index 0f27007be7..d6f4ae4ee8 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/ui/TransactionDetailsScreen.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/ui/TransactionDetailsScreen.kt @@ -47,8 +47,10 @@ fun TransactionDetailsScreen( ) { MMScreenEventLogger( - onScreenLand = { TransactionDetailsEventTrackerImpl.onTransactionDetailsScreenLanded() }, - onScreenExit = { TransactionDetailsEventTrackerImpl.onTransactionDetailsScreenExit() }, + onScreenLand = { + viewModel.transactionHistoryEventTracker.onTransactionDetailsScreenLanded() + }, + onScreenExit = { viewModel.transactionHistoryEventTracker.onTransactionDetailsScreenExit() }, ) val activity = LocalContext.current as MMActivity @@ -101,6 +103,7 @@ fun TransactionDetailsScreen( bottomSheet = bottomSheet, onEvent = { viewModel.sendEvent(it) }, onEffect = { viewModel.setEffect { it } }, + getEventTracker = { viewModel.transactionHistoryEventTracker }, ) } } @@ -110,6 +113,7 @@ private fun TransactionDetailsBottomSheetContentHandler( bottomSheet: TransactionDetailsScreenBottomSheets, onEvent: (TransactionDetailsScreenUiEvent) -> Unit, onEffect: (TransactionDetailsScreenUiEffect) -> Unit, + getEventTracker: () -> TransactionDetailsEventTrackerImpl, ) { if (bottomSheet.sheetData == null) return val onDismissAction = bottomSheet.createDismissAction(onEvent) @@ -133,6 +137,7 @@ private fun TransactionDetailsBottomSheetContentHandler( ) }, onDismiss = onDismissAction, + getEventTracker = getEventTracker, ) } TransactionDetailsScreenBottomSheets.NoBottomSheet -> {} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/viewModel/TransactionDetailsViewModel.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/viewModel/TransactionDetailsViewModel.kt index a129789760..86810ef92f 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/viewModel/TransactionDetailsViewModel.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactiondetails/viewModel/TransactionDetailsViewModel.kt @@ -10,7 +10,10 @@ package com.navi.moneymanager.postonboard.transactiondetails.viewModel import androidx.lifecycle.viewModelScope import com.navi.moneymanager.base.viewmodel.MMBaseViewModel import com.navi.moneymanager.common.analytics.DataProviderEventTrackerImpl +import com.navi.moneymanager.common.analytics.TransactionDetailsEventTrackerImpl +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper import com.navi.moneymanager.common.navigation.utils.MMScreen +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE import com.navi.moneymanager.postonboard.help.model.HelpBottomSheetState import com.navi.moneymanager.postonboard.help.usecase.ManageConsentUseCase import com.navi.moneymanager.postonboard.transactiondetails.model.TransactionDetailsScreenUiEffect @@ -28,6 +31,7 @@ class TransactionDetailsViewModel constructor( val repository: TransactionDetailsScreenRepository, private val manageConsentUseCase: ManageConsentUseCase, + private val mmConfigResponseHelper: MMConfigResponseHelper, ) : MMBaseViewModel< TransactionDetailsScreenUiState, @@ -37,11 +41,22 @@ constructor( initialState = TransactionDetailsScreenUiState.initialState, reducer = TransactionDetailsScreenReducer(), ) { + val transactionHistoryEventTracker by lazy { + TransactionDetailsEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + + val dataProviderEventTracker by lazy { + DataProviderEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } fun fetchTransactionDetailScreenData(transactionId: String) { viewModelScope.safeLaunch(Dispatchers.IO) { repository.getTransactionDetailScreenData(transactionId).collect { - DataProviderEventTrackerImpl.transactionDetailsVmCollect() + dataProviderEventTracker.transactionDetailsVmCollect() sendEvent(TransactionDetailsScreenUiEvent.UpdateTransactionDetailsData(it)) } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactionhistory/ui/TransactionHistoryBottomSheetContent.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactionhistory/ui/TransactionHistoryBottomSheetContent.kt index cc1eaf6e0f..9d5e54bff2 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactionhistory/ui/TransactionHistoryBottomSheetContent.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactionhistory/ui/TransactionHistoryBottomSheetContent.kt @@ -71,6 +71,7 @@ internal fun TransactionHistoryBottomSheetContent( stateProvider: () -> TransactionHistoryScreenUiState, eventHandler: (TransactionHistoryScreenUiEvent) -> Unit, effectHandler: (TransactionHistoryScreenUiEffect) -> Unit, + getEventTracker: () -> TransactionHistoryEventTrackerImpl, ) { val state by remember { derivedStateOf { stateProvider() } } val filterData by remember(state) { mutableStateOf(state.filterOptionData) } @@ -195,8 +196,7 @@ internal fun TransactionHistoryBottomSheetContent( loading = false, text = context.getString(R.string.clear_all), onClick = { - TransactionHistoryEventTrackerImpl - .onTransactionHistoryFilterBottomsheetClearAllClicked() + getEventTracker().onTransactionHistoryFilterBottomsheetClearAllClicked() eventHandler(TransactionHistoryScreenUiEvent.ClearAllSelection) }, lottieSpec = LottieCompositionSpec.Url(EMPTY), @@ -226,9 +226,10 @@ internal fun TransactionHistoryBottomSheetContent( loading = false, text = applyButtonLabel, onClick = { - TransactionHistoryEventTrackerImpl.onTransactionHistoryFilterApplied( - filterData.availableFilters.map { it.selectedValueCount }.toString() - ) + getEventTracker() + .onTransactionHistoryFilterApplied( + filterData.availableFilters.map { it.selectedValueCount }.toString() + ) eventHandler( TransactionHistoryScreenUiEvent.OnFilterApplied(selectedFilterOptions) ) diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactionhistory/ui/TransactionHistoryContent.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactionhistory/ui/TransactionHistoryContent.kt index 9231d3e6ee..e862deff32 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactionhistory/ui/TransactionHistoryContent.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactionhistory/ui/TransactionHistoryContent.kt @@ -88,6 +88,7 @@ internal fun TransactionHistoryContent( monthlyBalanceData = monthlyBalanceData, onTransactionClick = onTransactionClick, onTransactionCategoryClick = onTransactionCategoryClick, + transactionHistoryEventTracker = viewModelProvider().transactionHistoryEventTracker, ) } } @@ -98,6 +99,7 @@ private fun TransactionList( monthlyBalanceData: List, onTransactionClick: (String) -> Unit, onTransactionCategoryClick: (Transaction) -> Unit, + transactionHistoryEventTracker: TransactionHistoryEventTrackerImpl, ) { LazyColumn( modifier = Modifier.fillMaxWidth().fillMaxHeight(), @@ -144,15 +146,14 @@ private fun TransactionList( Transaction( transaction = transaction, onClick = { transactionId -> - TransactionHistoryEventTrackerImpl - .onTransactionHistoryTransactionClicked( - source = TRANSACTION_HISTORY, - transactionRank = index + 1, - ) + transactionHistoryEventTracker.onTransactionHistoryTransactionClicked( + source = TRANSACTION_HISTORY, + transactionRank = index + 1, + ) onTransactionClick(transactionId) }, onCategoryClick = { - TransactionHistoryEventTrackerImpl + transactionHistoryEventTracker .onTransactionHistoryTransactionCategoryClicked( source = TRANSACTION_HISTORY, transactionRank = index + 1, @@ -186,6 +187,7 @@ private fun TransactionList( ) } } + is LoadState.Error -> {} else -> Unit } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactionhistory/ui/TransactionHistoryScaffold.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactionhistory/ui/TransactionHistoryScaffold.kt index 585c1cb2c6..17d94d77b0 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactionhistory/ui/TransactionHistoryScaffold.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactionhistory/ui/TransactionHistoryScaffold.kt @@ -52,7 +52,6 @@ import com.navi.design.utils.NoRippleIndicationSource import com.navi.elex.theme.elexColors import com.navi.elex.utils.setOnClick import com.navi.moneymanager.R -import com.navi.moneymanager.common.analytics.TransactionHistoryEventTrackerImpl import com.navi.moneymanager.common.illustration.model.IllustrationSource import com.navi.moneymanager.common.illustration.model.IllustrationType import com.navi.moneymanager.common.illustration.model.ImageProperties @@ -108,10 +107,13 @@ internal fun TransactionHistoryScaffold( placeholderText = stringResource(R.string.mm_transaction_search_placeholder), isFilterApplied = isFilterApplied, onFilterClick = { - TransactionHistoryEventTrackerImpl.onTransactionHistoryFilterIconClicked() + getViewModel() + .transactionHistoryEventTracker + .onTransactionHistoryFilterIconClicked() getViewModel() .sendEvent(TransactionHistoryScreenUiEvent.ShowFilterBottomSheet) }, + getEventTracker = { getViewModel().transactionHistoryEventTracker }, ) FilterSection( filters = transactionHistoryState().selectedFilterOption.flatMap { it.value }, @@ -197,9 +199,9 @@ private fun FilterSection( selectedFilterItems?.mapIndexed { index, item -> if (item.type is FilterItemData.ValueData.FilterChipType.ChipItem) { SingleFilterItem(item) { - TransactionHistoryEventTrackerImpl.onTransactionHistoryFilterRemoved( - filterRank = index + 1 - ) + getViewModel() + .transactionHistoryEventTracker + .onTransactionHistoryFilterRemoved(filterRank = index + 1) getViewModel() .sendEvent(TransactionHistoryScreenUiEvent.DeleteFilterItem(it)) } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactionhistory/ui/TransactionHistoryScreen.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactionhistory/ui/TransactionHistoryScreen.kt index 323f05c437..4d686908ec 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactionhistory/ui/TransactionHistoryScreen.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactionhistory/ui/TransactionHistoryScreen.kt @@ -28,7 +28,6 @@ import com.navi.base.utils.orFalse import com.navi.common.navigation.NavigationAction import com.navi.elex.atoms.ElexBottomSheet import com.navi.elex.theme.elexColors -import com.navi.moneymanager.common.analytics.TransactionHistoryEventTrackerImpl import com.navi.moneymanager.common.illustration.model.IllustrationSource import com.navi.moneymanager.common.illustration.model.IllustrationType import com.navi.moneymanager.common.navigation.utils.MMScreen @@ -60,8 +59,10 @@ fun TransactionHistoryScreen( viewModel: TransactionHistoryViewModel = hiltViewModel(), ) { MMScreenEventLogger( - onScreenLand = { TransactionHistoryEventTrackerImpl.onTransactionHistoryScreenLanded() }, - onScreenExit = { TransactionHistoryEventTrackerImpl.onTransactionHistoryScreenExit() }, + onScreenLand = { + viewModel.transactionHistoryEventTracker.onTransactionHistoryScreenLanded() + }, + onScreenExit = { viewModel.transactionHistoryEventTracker.onTransactionHistoryScreenExit() }, ) val state by viewModel.state.collectAsStateWithLifecycle() @@ -143,6 +144,7 @@ fun TransactionHistoryScreen( stateProvider = { state }, eventHandler = viewModel::sendEvent, effectHandler = { effect -> viewModel.setEffect { effect } }, + getEventTracker = { viewModel.transactionHistoryEventTracker }, ) } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactionhistory/viewmodel/TransactionHistoryViewModel.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactionhistory/viewmodel/TransactionHistoryViewModel.kt index 5b68baac87..dfc25b856b 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactionhistory/viewmodel/TransactionHistoryViewModel.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/transactionhistory/viewmodel/TransactionHistoryViewModel.kt @@ -14,9 +14,12 @@ import androidx.paging.cachedIn import com.navi.base.utils.EMPTY import com.navi.moneymanager.base.viewmodel.MMBaseViewModel import com.navi.moneymanager.common.analytics.DataProviderEventTrackerImpl +import com.navi.moneymanager.common.analytics.TransactionHistoryEventTrackerImpl +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper import com.navi.moneymanager.common.model.FilterItemData import com.navi.moneymanager.common.model.MonthlyBalance import com.navi.moneymanager.common.model.Transaction +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE import com.navi.moneymanager.postonboard.transactionhistory.model.TransactionFilterData import com.navi.moneymanager.postonboard.transactionhistory.model.TransactionHistoryScreenUiEffect import com.navi.moneymanager.postonboard.transactionhistory.model.TransactionHistoryScreenUiEvent @@ -45,7 +48,10 @@ typealias GroupedFilterOptions = List>> @HiltViewModel class TransactionHistoryViewModel @Inject -constructor(val repository: TransactionHistoryRepository) : +constructor( + val repository: TransactionHistoryRepository, + val mmConfigResponseHelper: MMConfigResponseHelper, +) : MMBaseViewModel< TransactionHistoryScreenUiState, TransactionHistoryScreenUiEvent, @@ -57,6 +63,18 @@ constructor(val repository: TransactionHistoryRepository) : private val _searchQuery = MutableStateFlow(TextFieldValue(text = EMPTY)) val searchQuery = _searchQuery.asStateFlow() + private val dataProviderEventTracker by lazy { + DataProviderEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + + val transactionHistoryEventTracker by lazy { + TransactionHistoryEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) val transactionPagingSourceFlow = combine(_searchQuery.debounce(300), state.map { it.selectedFilterOption }) { @@ -66,7 +84,7 @@ constructor(val repository: TransactionHistoryRepository) : } .distinctUntilChanged() .flatMapLatest { (query, filterData) -> - DataProviderEventTrackerImpl.transactionHistoryVmFetchData(query, filterData) + dataProviderEventTracker.transactionHistoryVmFetchData(query, filterData) fetchTransactionPagingSource(query.text, state.value.filterOptionData to filterData) } .cachedIn(viewModelScope) @@ -83,7 +101,7 @@ constructor(val repository: TransactionHistoryRepository) : appliedFilters = appliedFilters, allFilters = allFilters, ) { monthlyBalanceData -> - DataProviderEventTrackerImpl.transactionHistoryVmCollect(monthlyBalanceData.size) + dataProviderEventTracker.transactionHistoryVmCollect(monthlyBalanceData.size) updateMonthlyBalance(monthlyBalanceData) } } @@ -109,7 +127,7 @@ constructor(val repository: TransactionHistoryRepository) : fun onSearchQueryChanged(query: TextFieldValue) { _searchQuery.update { query } - DataProviderEventTrackerImpl.transactionHistoryVmUpdate(query) + dataProviderEventTracker.transactionHistoryVmUpdate(query) } fun getZeroTransactionData() { diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/finarkein/viewmodel/AccountLinkingStatusViewModel.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/finarkein/viewmodel/AccountLinkingStatusViewModel.kt index ba34ece72c..28fac40c6d 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/finarkein/viewmodel/AccountLinkingStatusViewModel.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/finarkein/viewmodel/AccountLinkingStatusViewModel.kt @@ -24,6 +24,7 @@ import com.navi.moneymanager.common.illustration.repository.LottieRepository.Com import com.navi.moneymanager.common.model.database.AccountOverview import com.navi.moneymanager.common.network.di.RoomDataStoreInfoProvider import com.navi.moneymanager.common.network.model.AccountData +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE import com.navi.moneymanager.common.utils.Constants.LAST_REFRESH_SUCCESSFUL_TIMESTAMP import com.navi.moneymanager.preonboard.finarkein.model.AccountLinkingData import com.navi.moneymanager.preonboard.finarkein.model.AccountLinkingStatusScreenUiEffect @@ -60,18 +61,24 @@ constructor( private lateinit var taskRepeater: TaskRepeater private lateinit var existingBankAccounts: List + private val accountLinkingEventTracker by lazy { + AccountLinkingEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to configResponseHelper.journeySource) + ) + } + init { viewModelScope.safeLaunch(Dispatchers.IO) { existingBankAccounts = bankAccountsDataProvider.getBankAccounts().first() val mmConfig = configResponseHelper.getMMConfig()?.accountLinkingPollingConfig - AccountLinkingEventTrackerImpl.onExistingAccountsAndConfigFetched() + accountLinkingEventTracker.onExistingAccountsAndConfigFetched() taskRepeater = getTaskRepeater( maxAttempts = mmConfig?.maxAttempts.or(10), taskIntervalSeconds = mmConfig?.taskIntervalSeconds.or(1), initialDelay = mmConfig?.initialDelaySeconds.or(0), ) - AccountLinkingEventTrackerImpl.onPollingStarted() + accountLinkingEventTracker.onPollingStarted() taskRepeater.startTask() } } @@ -86,7 +93,7 @@ constructor( taskIntervalSeconds = taskIntervalSeconds, initialDelaySeconds = initialDelay, onTimeout = { - AccountLinkingEventTrackerImpl.onAccountLinkingFailure() + accountLinkingEventTracker.onAccountLinkingFailure() setEffect { AccountLinkingStatusScreenUiEffect.Navigation.ErrorAndExit } }, task = { checkAccountLinkingStatus() }, @@ -113,7 +120,7 @@ constructor( } private fun sendSuccessEvent() { - AccountLinkingEventTrackerImpl.onAccountLinkingSuccess() + accountLinkingEventTracker.onAccountLinkingSuccess() sendEvent( AccountLinkingStatusScreenUiEvent.ApiStatus( AccountLinkingSuccessState.Success( diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/launcher/ui/ConsentRevokedBottomSheet.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/launcher/ui/ConsentRevokedBottomSheet.kt index 8dc8a4b096..ab32ce70b2 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/launcher/ui/ConsentRevokedBottomSheet.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/launcher/ui/ConsentRevokedBottomSheet.kt @@ -38,12 +38,14 @@ import com.navi.moneymanager.common.ui.composable.base.MMText import com.navi.moneymanager.preonboard.launcher.model.ConsentRevokedBottomSheetData @Composable -fun ConsentRevokedBottomSheetContent(onDismiss: () -> Unit, data: ConsentRevokedBottomSheetData) { - LaunchedEffect(Unit) { - ValuePropEventTrackerImpl.onValuePropConsentRevokedBottomSheetAppeared() - } +fun ConsentRevokedBottomSheetContent( + onDismiss: () -> Unit, + data: ConsentRevokedBottomSheetData, + getEventTracker: () -> ValuePropEventTrackerImpl, +) { + LaunchedEffect(Unit) { getEventTracker().onValuePropConsentRevokedBottomSheetAppeared() } DisposableEffect(Unit) { - onDispose { ValuePropEventTrackerImpl.onValuePropConsentRevokedBottomSheetDisappeared() } + onDispose { getEventTracker().onValuePropConsentRevokedBottomSheetDisappeared() } } Column( modifier = diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/launcher/ui/GenericErrorBottomSheet.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/launcher/ui/GenericErrorBottomSheet.kt index 26a727d928..785aec7290 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/launcher/ui/GenericErrorBottomSheet.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/launcher/ui/GenericErrorBottomSheet.kt @@ -30,12 +30,10 @@ import androidx.compose.ui.unit.sp import com.navi.common.utils.onClickWithDebounce import com.navi.design.font.FontWeightEnum import com.navi.elex.theme.elexColors -import com.navi.moneymanager.common.analytics.DashboardEventTrackerImpl -import com.navi.moneymanager.common.analytics.ValuePropEventTrackerImpl +import com.navi.moneymanager.common.analytics.GenericErrorBottomSheetEventTracker import com.navi.moneymanager.common.illustration.model.IllustrationSource import com.navi.moneymanager.common.illustration.model.IllustrationType import com.navi.moneymanager.common.illustration.ui.Illustration -import com.navi.moneymanager.common.navigation.utils.MMScreen import com.navi.moneymanager.common.ui.composable.base.MMText import com.navi.moneymanager.preonboard.launcher.model.GenericErrorBottomSheetData import com.navi.naviwidgets.utils.NaviWidgetIconUtils.ICON_ERROR_OUTLINED_ROUND_EXCLAMATION @@ -45,26 +43,13 @@ fun GenericErrorBottomSheet( onDismiss: () -> Unit, data: GenericErrorBottomSheetData, screenName: String, + getEventTracker: () -> GenericErrorBottomSheetEventTracker, ) { - LaunchedEffect(Unit) { - when (screenName) { - MMScreen.VALUE_PROP_SCREEN.name -> - ValuePropEventTrackerImpl.onValuePropGenericErrorBottomSheetAppeared() - MMScreen.DASHBOARD.name -> - DashboardEventTrackerImpl.onDashboardGenericErrorBottomSheetAppeared() - } - } + LaunchedEffect(Unit) { getEventTracker().onGenericErrorBottomSheetAppeared(screenName) } DisposableEffect(Unit) { - onDispose { - when (screenName) { - MMScreen.VALUE_PROP_SCREEN.name -> - ValuePropEventTrackerImpl.onValuePropGenericErrorBottomSheetDisappeared() - MMScreen.DASHBOARD.name -> - DashboardEventTrackerImpl.onDashboardGenericErrorBottomSheetDisappeared() - } - } + onDispose { getEventTracker().onGenericErrorBottomSheetDisappeared(screenName) } } Column( diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/launcher/ui/LauncherErrorScreen.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/launcher/ui/LauncherErrorScreen.kt index 21a8bd718e..6b7537fca6 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/launcher/ui/LauncherErrorScreen.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/launcher/ui/LauncherErrorScreen.kt @@ -43,11 +43,12 @@ fun LauncherErrorScreen( errorDetails: ErrorDetails, onTryAgainClick: () -> Unit, onBackPressClick: () -> Unit, + getEventTracker: () -> LauncherErrorEventTrackerImpl, ) { MMScreenEventLogger( - onScreenLand = { LauncherErrorEventTrackerImpl.onLauncherErrorScreenLanded() }, - onScreenExit = { LauncherErrorEventTrackerImpl.onLauncherErrorScreenExit() }, + onScreenLand = { getEventTracker().onLauncherErrorScreenLanded() }, + onScreenExit = { getEventTracker().onLauncherErrorScreenExit() }, ) val errorTitleText = errorDetails.titleMessage diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/launcher/ui/LauncherScreen.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/launcher/ui/LauncherScreen.kt index 8c87a0ba1e..7a9acf7491 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/launcher/ui/LauncherScreen.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/launcher/ui/LauncherScreen.kt @@ -22,7 +22,6 @@ import com.navi.base.model.CtaData import com.navi.base.utils.orFalse import com.navi.common.navigation.NavigationAction import com.navi.elex.theme.elexColors -import com.navi.moneymanager.common.analytics.LauncherEventTrackerImpl import com.navi.moneymanager.common.navigation.utils.MMScreen import com.navi.moneymanager.common.ui.composable.ScreenInit import com.navi.moneymanager.common.utils.Constants.IS_CONSENT_REVOKED @@ -47,8 +46,8 @@ fun LauncherScreen( ScreenInit(screenName = MMScreen.LAUNCHER.screen, activity = activity) MMScreenEventLogger( - onScreenLand = { LauncherEventTrackerImpl.onLauncherScreenLanded() }, - onScreenExit = { LauncherEventTrackerImpl.onLauncherScreenExit() }, + onScreenLand = { viewModel.launcherEventTracker.onLauncherScreenLanded() }, + onScreenExit = { viewModel.launcherEventTracker.onLauncherScreenExit() }, ) LaunchedEffect(Unit) { @@ -98,6 +97,7 @@ fun LauncherScreen( onBackPressClick = { viewModel.setEffect { LauncherScreenUiEffect.Navigation.Back } }, + getEventTracker = { viewModel.launcherEventErrorTracker }, ) } } @@ -111,6 +111,7 @@ fun LauncherScreen( onBackPressClick = { viewModel.setEffect { LauncherScreenUiEffect.Navigation.Back } }, + getEventTracker = { viewModel.launcherEventErrorTracker }, ) } } diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/launcher/viewmodel/LauncherViewModel.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/launcher/viewmodel/LauncherViewModel.kt index 0b479d4688..62ee059968 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/launcher/viewmodel/LauncherViewModel.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/launcher/viewmodel/LauncherViewModel.kt @@ -9,7 +9,11 @@ package com.navi.moneymanager.preonboard.launcher.viewmodel import androidx.lifecycle.viewModelScope import com.navi.moneymanager.base.viewmodel.MMBaseViewModel +import com.navi.moneymanager.common.analytics.LauncherErrorEventTrackerImpl +import com.navi.moneymanager.common.analytics.LauncherEventTrackerImpl +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper import com.navi.moneymanager.common.manager.MMLibManager +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE import com.navi.moneymanager.preonboard.launcher.model.ApiType import com.navi.moneymanager.preonboard.launcher.model.LauncherScreenUiEffect import com.navi.moneymanager.preonboard.launcher.model.LauncherScreenUiEvent @@ -27,12 +31,25 @@ class LauncherViewModel constructor( private val launcherRepository: Lazy, private val mmLibManager: MMLibManager, + val mmConfigResponseHelper: MMConfigResponseHelper, ) : MMBaseViewModel( initialState = LauncherScreenUiState.initialState, reducer = LauncherScreenReducer(), ) { + val launcherEventTracker by lazy { + LauncherEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + + val launcherEventErrorTracker by lazy { + LauncherErrorEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + fun fetchData(isConsentRevoked: Boolean) { viewModelScope.safeLaunch(Dispatchers.IO) { if (isConsentRevoked) { diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/valueprop/ui/ValuePropScreen.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/valueprop/ui/ValuePropScreen.kt index 02861837d4..532ab4bad3 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/valueprop/ui/ValuePropScreen.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/valueprop/ui/ValuePropScreen.kt @@ -52,8 +52,8 @@ fun ValuePropScreen( ScreenInit(screenName = MMScreen.VALUE_PROP_SCREEN.screen, activity = activity) MMScreenEventLogger( - onScreenLand = { ValuePropEventTrackerImpl.onValuePropScreenLanded() }, - onScreenExit = { ValuePropEventTrackerImpl.onValuePropScreenExit() }, + onScreenLand = { viewModel.valuePropEventTracker.onValuePropScreenLanded() }, + onScreenExit = { viewModel.valuePropEventTracker.onValuePropScreenExit() }, ) LaunchedEffect(Unit) { @@ -163,6 +163,7 @@ fun ValuePropScreen( bottomSheet = bottomSheet, onEvent = { viewModel.sendEvent(it) }, onEffect = { viewModel.setEffect { it } }, + getEventTracker = { viewModel.valuePropEventTracker }, ) } @@ -185,6 +186,7 @@ fun ValuePropBottomSheetContentHandler( bottomSheet: ValuePropScreenBottomSheets, onEvent: (ValuePropScreenUiEvent) -> Unit, onEffect: (ValuePropScreenUiEffect) -> Unit, + getEventTracker: () -> ValuePropEventTrackerImpl, ) { if (bottomSheet.sheetData == null) return val onDismissAction = bottomSheet.createDismissAction(onEvent) @@ -206,19 +208,23 @@ fun ValuePropBottomSheetContentHandler( onEffect(ValuePropScreenUiEffect.Navigation.DismissFinarkeinBottomSheet) onDismissAction() }, + getEventTracker = getEventTracker, ) } is ValuePropScreenBottomSheets.ConsentRevokedBottomSheet -> { ConsentRevokedBottomSheetContent( data = bottomSheet.data!!, onDismiss = { onDismissAction() }, + getEventTracker = getEventTracker, ) } + is ValuePropScreenBottomSheets.GenericErrorBottomSheet -> { GenericErrorBottomSheet( data = bottomSheet.data!!, onDismiss = { onDismissAction() }, screenName = MMScreen.VALUE_PROP_SCREEN.name, + getEventTracker = getEventTracker, ) } ValuePropScreenBottomSheets.NoBottomSheet -> {} diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/valueprop/viewmodel/ValuePropViewModel.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/valueprop/viewmodel/ValuePropViewModel.kt index b21afdf1e4..e9577d005a 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/valueprop/viewmodel/ValuePropViewModel.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/preonboard/valueprop/viewmodel/ValuePropViewModel.kt @@ -11,7 +11,10 @@ import androidx.lifecycle.viewModelScope import com.navi.common.network.models.isSuccessWithData import com.navi.common.uitron.model.action.MMFetchFinarkeinData import com.navi.moneymanager.base.viewmodel.MMBaseViewModel +import com.navi.moneymanager.common.analytics.ValuePropEventTrackerImpl +import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper import com.navi.moneymanager.common.model.bottomSheet.ValuePropScreenBottomSheets +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE import com.navi.moneymanager.common.utils.checkFinarkeinDataValidity import com.navi.moneymanager.common.utils.getGenericErrorBottomSheetData import com.navi.moneymanager.preonboard.finarkein.model.FinarkeinDataState @@ -31,6 +34,7 @@ class ValuePropViewModel constructor( private val actionsHandler: ActionsHandler, private val valuePropScreenRepository: ValuePropScreenRepository, + val mmConfigResponseHelper: MMConfigResponseHelper, ) : MMBaseViewModel( initialState = ValuePropScreenUiState.initialState, @@ -44,6 +48,12 @@ constructor( _triggerApiAction = value } + val valuePropEventTracker by lazy { + ValuePropEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to mmConfigResponseHelper.journeySource) + ) + } + init { viewModelScope.safeLaunch(Dispatchers.IO) { observeActionCallback() } } diff --git a/android/navi-money-manager/src/main/res/values/strings.xml b/android/navi-money-manager/src/main/res/values/strings.xml index ce6b9a0c00..7a6d2efc21 100644 --- a/android/navi-money-manager/src/main/res/values/strings.xml +++ b/android/navi-money-manager/src/main/res/values/strings.xml @@ -172,8 +172,25 @@ %) Spend goal index : Modify + Submit "Spend goal : " More Details Transaction Details + YOUR TOP SPEND IN %1$s + %1$s of all spends + View breakdown + NO SPENDS YET + No breakdown available for this month yet. Check past spends + View past breakdown + Tell us how we\'re doing? + What\'s working and what isn\'t? + Share Feedback + Tell us how we can improve + Your feedback matters. Share your concerns to help us improve your UPI experience. + Thank you for your feedback! + Navi UPI + UPI + UPI transaction ID + Navi transaction ID diff --git a/android/navi-pay/build.gradle b/android/navi-pay/build.gradle index a31bdd2faf..009c7cc965 100644 --- a/android/navi-pay/build.gradle +++ b/android/navi-pay/build.gradle @@ -86,6 +86,7 @@ static def formatString(String value) { dependencies { implementation project(":navi-common") implementation project(":navi-rr") + implementation project(":navi-money-manager") implementation libs.accompanist.systemuicontroller implementation libs.android.material implementation libs.androidx.appcompat diff --git a/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/db/dao/OrderDao.kt b/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/db/dao/OrderDao.kt index db87ade9e9..95ce2c8b3c 100644 --- a/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/db/dao/OrderDao.kt +++ b/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/db/dao/OrderDao.kt @@ -80,6 +80,9 @@ interface OrderDao { ) fun getOldestOrderTimestamp(): Flow + @Query("SELECT MAX(orderTimestamp) FROM $NAVI_PAY_DATABASE_T_STORE_ORDER_HISTORY_TABLE_NAME") + fun getLatestOrderTimestamp(): Flow + @Query("DELETE FROM $NAVI_PAY_DATABASE_T_STORE_ORDER_HISTORY_TABLE_NAME") suspend fun deleteAll() diff --git a/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/model/view/OrderHistoryBottomsheetType.kt b/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/model/view/OrderHistoryBottomsheetType.kt new file mode 100644 index 0000000000..cac24b1d19 --- /dev/null +++ b/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/model/view/OrderHistoryBottomsheetType.kt @@ -0,0 +1,17 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.pay.tstore.list.model.view + +sealed class OrderHistoryBottomsheetType { + + data class UpiSpendAnalyzerInfo( + val upiSpendAnalyzerInfoBottomsheetData: UpiSpendAnalyzerInfoBottomsheetData + ) : OrderHistoryBottomsheetType() + + data object TransactionsFilter : OrderHistoryBottomsheetType() +} diff --git a/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/model/view/OrderHistoryScreenBottomSheetStateHolder.kt b/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/model/view/OrderHistoryScreenBottomSheetStateHolder.kt new file mode 100644 index 0000000000..792e59133e --- /dev/null +++ b/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/model/view/OrderHistoryScreenBottomSheetStateHolder.kt @@ -0,0 +1,13 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.pay.tstore.list.model.view + +data class OrderHistoryScreenBottomSheetStateHolder( + val showBottomSheet: Boolean, + val bottomSheetType: OrderHistoryBottomsheetType?, +) diff --git a/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/model/view/UpiSpendAnalyzerInfoBottomsheetData.kt b/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/model/view/UpiSpendAnalyzerInfoBottomsheetData.kt new file mode 100644 index 0000000000..d91537bc7e --- /dev/null +++ b/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/model/view/UpiSpendAnalyzerInfoBottomsheetData.kt @@ -0,0 +1,13 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.pay.tstore.list.model.view + +data class UpiSpendAnalyzerInfoBottomsheetData( + val date: String? = null, + val amount: String? = null, +) diff --git a/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/repository/OrderRepository.kt b/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/repository/OrderRepository.kt index 52282c0089..8320a3d517 100644 --- a/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/repository/OrderRepository.kt +++ b/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/repository/OrderRepository.kt @@ -305,6 +305,10 @@ constructor( return orderDao.getOrderEntitiesByOffset(limit = limit, offset = offset) } + fun getLatestOrderTimestamp(): Flow { + return orderDao.getLatestOrderTimestamp() + } + private suspend fun insertOrderEntityListDataIntoVpaTransactionInsightsDb( orderEntityList: List ) { diff --git a/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/ui/OrderHistoryBottomSheetContent.kt b/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/ui/OrderHistoryBottomSheetContent.kt index 1bcb1996b2..cafbcb69fc 100644 --- a/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/ui/OrderHistoryBottomSheetContent.kt +++ b/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/ui/OrderHistoryBottomSheetContent.kt @@ -12,6 +12,7 @@ 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.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight @@ -28,6 +29,7 @@ import androidx.compose.material.CheckboxDefaults import androidx.compose.material.Divider import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -43,14 +45,20 @@ import com.navi.common.R as CommonR import com.navi.design.font.FontWeightEnum import com.navi.design.font.getFontWeight import com.navi.design.font.naviFontFamily +import com.navi.moneymanager.common.analytics.UpiTransactionHistorySpendAnalyserEventTracker +import com.navi.moneymanager.common.helper.convertMonthStringToPair import com.navi.naviwidgets.extensions.NaviText import com.navi.pay.R import com.navi.pay.common.theme.color.NaviPayColor import com.navi.pay.common.ui.PrimaryAndSecondaryButton +import com.navi.pay.common.ui.ThemeRoundedButton import com.navi.pay.management.transactionhistory.model.view.TransactionTagHolder import com.navi.pay.tstore.list.model.view.FilterTab +import com.navi.pay.tstore.list.model.view.OrderHistoryBottomsheetType +import com.navi.pay.tstore.list.model.view.UpiSpendAnalyzerInfoBottomsheetData import com.navi.pay.tstore.list.viewmodel.FilterTabBottomSheetUIState import com.navi.pay.utils.RESOURCE_DEFAULT_ID +import com.navi.pay.utils.RUPEE_SYMBOL import com.navi.pay.utils.clickableDebounce import com.navi.pay.utils.noRippleClickableWithDebounce import com.navi.rr.utils.ext.clickable @@ -69,12 +77,68 @@ fun OrderHistoryBottomSheetContent( selectedFilterTagsCount: Int, filterTabsData: List, filterTabBottomSheetUIState: FilterTabBottomSheetUIState, + bottomSheetContentType: OrderHistoryBottomsheetType?, + upiTransactionHistorySpendAnalyserEventTracker: UpiTransactionHistorySpendAnalyserEventTracker, onFilterTabClicked: (Int) -> Unit, onFilterTagsApplyAllClicked: () -> Unit, onFilterTagsClearAllClicked: () -> Unit, closeSheet: () -> Unit, ) { + when (bottomSheetContentType) { + OrderHistoryBottomsheetType.TransactionsFilter -> { + TransactionFilterBottomSheetContent( + closeSheet = closeSheet, + filterTabsData = filterTabsData, + selectedTabIndex = selectedTabIndex, + onFilterTabClicked = onFilterTabClicked, + filterTabBottomSheetUIState = filterTabBottomSheetUIState, + transactionStatusTagsHolder = transactionStatusTagsHolder, + onTransactionStatusTagCheckChanged = onTransactionStatusTagCheckChanged, + monthTagsHolder = monthTagsHolder, + onMonthTagCheckChanged = onMonthTagCheckChanged, + categoryTagsHolder = categoryTagsHolder, + onCategoryTagCheckChanged = onCategoryTagCheckChanged, + paymentModeTagsHolder = paymentModeTagsHolder, + onPaymentModeTagCheckChanged = onPaymentModeTagCheckChanged, + selectedFilterTagsCount = selectedFilterTagsCount, + onFilterTagsApplyAllClicked = onFilterTagsApplyAllClicked, + onFilterTagsClearAllClicked = onFilterTagsClearAllClicked, + ) + } + is OrderHistoryBottomsheetType.UpiSpendAnalyzerInfo -> { + LaunchedEffect(Unit) { + upiTransactionHistorySpendAnalyserEventTracker + .onUpiSpendAnalyzerInfoBottomSheetAppeared() + } + UpiSpendAnalyzerInfoBottomSheetContent( + upiSpendAnalyzerInfoBottomsheetData = + bottomSheetContentType.upiSpendAnalyzerInfoBottomsheetData, + closeSheet = closeSheet, + ) + } + else -> {} + } +} +@Composable +private fun TransactionFilterBottomSheetContent( + closeSheet: () -> Unit, + filterTabsData: List, + selectedTabIndex: Int, + onFilterTabClicked: (Int) -> Unit, + filterTabBottomSheetUIState: FilterTabBottomSheetUIState, + transactionStatusTagsHolder: List, + onTransactionStatusTagCheckChanged: (Int, Boolean) -> Unit, + monthTagsHolder: List, + onMonthTagCheckChanged: (Int, Boolean) -> Unit, + categoryTagsHolder: List, + onCategoryTagCheckChanged: (Int, Boolean) -> Unit, + paymentModeTagsHolder: List, + onPaymentModeTagCheckChanged: (Int, Boolean) -> Unit, + selectedFilterTagsCount: Int, + onFilterTagsApplyAllClicked: () -> Unit, + onFilterTagsClearAllClicked: () -> Unit, +) { Column(modifier = Modifier.fillMaxWidth()) { Row( modifier = Modifier.fillMaxWidth().padding(16.dp), @@ -122,18 +186,21 @@ fun OrderHistoryBottomSheetContent( onFilterTagsCheckChanged = onTransactionStatusTagCheckChanged, ) } + FilterTabBottomSheetUIState.Months -> { RenderFilterTagsBottomSheet( filterTags = monthTagsHolder, onFilterTagsCheckChanged = onMonthTagCheckChanged, ) } + FilterTabBottomSheetUIState.Categories -> { RenderFilterTagsBottomSheet( filterTags = categoryTagsHolder, onFilterTagsCheckChanged = onCategoryTagCheckChanged, ) } + FilterTabBottomSheetUIState.PaymentMode -> { RenderFilterTagsBottomSheet( filterTags = paymentModeTagsHolder, @@ -163,6 +230,50 @@ fun OrderHistoryBottomSheetContent( } } +@Composable +private fun UpiSpendAnalyzerInfoBottomSheetContent( + upiSpendAnalyzerInfoBottomsheetData: UpiSpendAnalyzerInfoBottomsheetData?, + closeSheet: () -> Unit, +) { + Column( + modifier = + Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 32.dp) + ) { + NaviText( + text = + "${upiSpendAnalyzerInfoBottomsheetData?.date} • $RUPEE_SYMBOL${upiSpendAnalyzerInfoBottomsheetData?.amount}", + color = NaviPayColor.textPrimary, + fontSize = 16.sp, + fontFamily = naviFontFamily, + lineHeight = 24.sp, + fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD), + ) + Spacer(modifier = Modifier.height(24.dp)) + NaviText( + text = + String.format( + stringResource(R.string.upi_spend_analyzer_bottomsheet_info_description), + convertMonthStringToPair(upiSpendAnalyzerInfoBottomsheetData?.date.orEmpty()) + .first, + convertMonthStringToPair(upiSpendAnalyzerInfoBottomsheetData?.date.orEmpty()) + .second, + ), + color = NaviPayColor.textPrimary, + fontSize = 14.sp, + lineHeight = 22.sp, + fontFamily = naviFontFamily, + fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), + ) + Spacer(modifier = Modifier.height(24.dp)) + ThemeRoundedButton( + text = stringResource(id = R.string.np_okay_got_it), + onClick = closeSheet, + modifier = Modifier.fillMaxWidth(), + paddingValues = PaddingValues(horizontal = 16.dp, vertical = 12.dp), + ) + } +} + @Composable fun FilterTabView( index: Int, diff --git a/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/ui/OrderHistoryScreen.kt b/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/ui/OrderHistoryScreen.kt index 55b74667a4..15a6311e7c 100644 --- a/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/ui/OrderHistoryScreen.kt +++ b/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/ui/OrderHistoryScreen.kt @@ -7,6 +7,7 @@ package com.navi.pay.tstore.list.ui +import android.os.Bundle import android.view.WindowManager import androidx.activity.compose.BackHandler import androidx.compose.foundation.Image @@ -24,6 +25,7 @@ import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items @@ -82,12 +84,23 @@ import androidx.paging.LoadState import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.itemKey import coil.compose.AsyncImage +import com.navi.base.deeplink.DeepLinkManager +import com.navi.base.model.CtaData +import com.navi.base.model.LineItem import com.navi.base.utils.DateUtils import com.navi.common.R as CommonR +import com.navi.common.utils.clickableDebounce import com.navi.design.font.FontWeightEnum import com.navi.design.font.getFontWeight import com.navi.design.font.naviFontFamily import com.navi.design.utils.maxScrollFlingBehavior +import com.navi.moneymanager.common.analytics.UpiTransactionHistorySpendAnalyserEventTrackerImpl +import com.navi.moneymanager.common.model.SelectedMonth +import com.navi.moneymanager.common.model.upi.UpiTxnHistorySpendAnalysisData +import com.navi.moneymanager.common.ui.composable.upi.UpiTransactionHistorySpendAnalyserWidget +import com.navi.moneymanager.common.utils.Constants.CONTROL +import com.navi.moneymanager.common.utils.Constants.JOURNEY_SOURCE +import com.navi.moneymanager.common.utils.Constants.SELECTED_MONTH import com.navi.naviwidgets.extensions.NaviText import com.navi.naviwidgets.extensions.isScrollingDown import com.navi.pay.R @@ -111,6 +124,8 @@ import com.navi.pay.management.common.sendmoney.model.view.PayeeTransactionHisto import com.navi.pay.management.transactionhistory.model.view.TransactionTagHolder import com.navi.pay.management.transactionhistory.ui.PayeeLevelTransactionHistoryShimmer import com.navi.pay.management.transactionhistory.ui.TransactionHistoryShimmer +import com.navi.pay.tstore.list.model.view.OrderHistoryBottomsheetType +import com.navi.pay.tstore.list.model.view.UpiSpendAnalyzerInfoBottomsheetData import com.navi.pay.tstore.list.viewmodel.OrderHistoryViewModel import com.navi.pay.tstore.list.viewmodel.OrderHistoryViewModel.Companion.MONTHS_FILTER_TAB_INDEX import com.navi.pay.utils.AT_THE_RATE_CHAR @@ -120,7 +135,6 @@ import com.navi.pay.utils.HYPHEN import com.navi.pay.utils.MAX_VISIBLE_TRANSACTION_ITEMS_IN_SCREEN_HEIGHT import com.navi.pay.utils.RESOURCE_DEFAULT_ID import com.navi.pay.utils.RUPEE_SYMBOL -import com.navi.pay.utils.clickableDebounce import com.navi.pay.utils.conditional import com.navi.pay.utils.contactInitials import com.navi.pay.utils.customHide @@ -179,7 +193,8 @@ fun OrderHistoryScreen( val selectedFilterTagsCount by orderHistoryViewModel.selectedFilterTagsCount.collectAsStateWithLifecycle() - val showBottomSheet by orderHistoryViewModel.showBottomSheet.collectAsStateWithLifecycle() + val upiSpendAnalysisWidgetVariant by + orderHistoryViewModel.upiSpendAnalysisWidgetVariant.collectAsStateWithLifecycle() val isSearchBarActive by orderHistoryViewModel.isSearchBarActive.collectAsStateWithLifecycle() @@ -189,12 +204,31 @@ fun OrderHistoryScreen( val cashToCoinsConversionAmount by orderHistoryViewModel.cashToCoinConversionAmount.collectAsStateWithLifecycle() + val upiTxnHistorySpendAnalysisData by + orderHistoryViewModel.upiTxnHistorySpendAnalysisData.collectAsStateWithLifecycle(null) + + val bottomSheetStateHolder by + orderHistoryViewModel.bottomSheetStateHolder.collectAsStateWithLifecycle() + + val upiTransactionHistorySpendAnalyserEventTracker by lazy { + UpiTransactionHistorySpendAnalyserEventTrackerImpl( + parameters = mapOf(JOURNEY_SOURCE to "NAVI_UPI") + ) + } + val dismissBottomSheet: () -> Unit = { scope .launch { bottomSheetState.hide() } .invokeOnCompletion { if (!bottomSheetState.isVisible) { - orderHistoryViewModel.updateBottomSheetState(showBottomSheet = false) + if ( + bottomSheetStateHolder.bottomSheetType + is OrderHistoryBottomsheetType.UpiSpendAnalyzerInfo + ) { + upiTransactionHistorySpendAnalyserEventTracker + .onUpiSpendAnalyzerInfoBottomSheetDisappeared() + } + orderHistoryViewModel.updateBottomSheetUiState(showBottomSheet = false) } } } @@ -337,11 +371,14 @@ fun OrderHistoryScreen( } orderHistoryViewModel.updateSelectedTabIndex(selectedIndex = MONTHS_FILTER_TAB_INDEX) orderHistoryViewModel.resetCheckedItemsToActiveStateItems() - orderHistoryViewModel.updateBottomSheetState(showBottomSheet = true) + orderHistoryViewModel.updateBottomSheetUiState( + showBottomSheet = true, + bottomSheetType = OrderHistoryBottomsheetType.TransactionsFilter, + ) naviPayAnalytics.onFiltersTabClicked(source = orderHistoryViewModel.screenSource) } - if (showBottomSheet) { + if (bottomSheetStateHolder.showBottomSheet) { NaviPayModalBottomSheet( modifier = Modifier.fillMaxWidth(), bottomSheetState = bottomSheetState, @@ -368,6 +405,9 @@ fun OrderHistoryScreen( onFilterTagsClearAllClicked = onFilterTagsClearAllClicked, selectedFilterTagsCount = selectedFilterTagsCount, closeSheet = dismissBottomSheet, + bottomSheetContentType = bottomSheetStateHolder.bottomSheetType, + upiTransactionHistorySpendAnalyserEventTracker = + upiTransactionHistorySpendAnalyserEventTracker, ) } } @@ -547,7 +587,6 @@ fun OrderHistoryScreen( format = DATE_TIME_FORMAT_MONTH_YEAR_WITH_SPACE_SEPARATOR, ) - if (index != orderHistoryItems.itemCount - 1) { val nextTransactionMonthYearDateAsString = @@ -576,6 +615,40 @@ fun OrderHistoryScreen( orderSummaryAmountMapData[ transactionHistoryItem.monthTag], showMonthlySummaryAmount = showMonthlySummaryAmount, + showInfoIcon = + upiTxnHistorySpendAnalysisData != null && + upiSpendAnalysisWidgetVariant != CONTROL, + ) { + upiTransactionHistorySpendAnalyserEventTracker + .onMonthlyViewInfoIconClicked() + orderHistoryViewModel.updateBottomSheetUiState( + showBottomSheet = true, + bottomSheetType = + OrderHistoryBottomsheetType + .UpiSpendAnalyzerInfo( + UpiSpendAnalyzerInfoBottomsheetData( + date = + currentMonthYearDateAsString, + amount = + orderSummaryAmountMapData[ + transactionHistoryItem + .monthTag], + ) + ), + ) + } + UpiSpendAnalysisWidget( + data = upiTxnHistorySpendAnalysisData, + variant = upiSpendAnalysisWidgetVariant, + naviPayActivity = naviPayActivity, + dismissWidget = { + upiTransactionHistorySpendAnalyserEventTracker + .onUpiSpendAnalyserWidgetCrossIconClicked() + orderHistoryViewModel + .dismissUpiSpendAnalysisWidget() + }, + upiTransactionHistorySpendAnalyserEventTracker = + upiTransactionHistorySpendAnalyserEventTracker, ) } else { val previousMonthYearDateAsString = @@ -710,6 +783,36 @@ fun OrderHistoryScreen( } } +@Composable +private fun UpiSpendAnalysisWidget( + data: UpiTxnHistorySpendAnalysisData?, + variant: String, + naviPayActivity: NaviPayActivity, + dismissWidget: () -> Unit, + upiTransactionHistorySpendAnalyserEventTracker: + UpiTransactionHistorySpendAnalyserEventTrackerImpl, +) { + data?.let { + if (variant != CONTROL) { + UpiTransactionHistorySpendAnalyserWidget( + variant = variant, + data = data, + onClick = { + upiTransactionHistorySpendAnalyserEventTracker + .onUpiTransactionHistorySpendAnalyserWidgetClicked(variant) + navigateToUpiSpendAnalysisScreen( + naviPayActivity = naviPayActivity, + selectedMonth = data.selectedMonth, + ) + }, + onCrossIconClick = dismissWidget, + upiTransactionHistorySpendAnalyserEventTracker = + upiTransactionHistorySpendAnalyserEventTracker, + ) + } + } +} + @Composable private fun SearchAndFilterSection( searchQuery: String, @@ -820,7 +923,13 @@ private fun FilterSection( } @Composable -fun NewMonthView(date: String, amount: String?, showMonthlySummaryAmount: Boolean) { +fun NewMonthView( + date: String, + amount: String?, + showMonthlySummaryAmount: Boolean, + showInfoIcon: Boolean = false, + onInfoIconClick: (() -> Unit)? = null, +) { Row( modifier = Modifier.fillMaxWidth() @@ -836,15 +945,29 @@ fun NewMonthView(date: String, amount: String?, showMonthlySummaryAmount: Boolea lineHeight = 22.sp, color = NaviPayColor.textPrimary, ) - - NaviText( - text = if (amount != null && showMonthlySummaryAmount) "$RUPEE_SYMBOL$amount" else "", - fontFamily = naviFontFamily, - fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD), - fontSize = 14.sp, - lineHeight = 22.sp, - color = NaviPayColor.textPrimary, - ) + Row( + modifier = Modifier.wrapContentWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + NaviText( + text = + if (amount != null && showMonthlySummaryAmount) "$RUPEE_SYMBOL$amount" else "", + fontFamily = naviFontFamily, + fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD), + fontSize = 14.sp, + lineHeight = 22.sp, + color = NaviPayColor.textPrimary, + ) + if (showInfoIcon && amount != null && showMonthlySummaryAmount) { + Spacer(modifier = Modifier.width(8.dp)) + Image( + painter = + painterResource(id = com.navi.naviwidgets.R.drawable.info_icon_purple), + contentDescription = "", + modifier = Modifier.size(20.dp).clickableDebounce { onInfoIconClick?.invoke() }, + ) + } + } } Spacer(modifier = Modifier.height(8.dp)) @@ -1052,3 +1175,22 @@ fun FilterChip(tag: TransactionTagHolder, onRemove: (TransactionTagHolder) -> Un iconResId = CommonR.drawable.ic_close_black, ) } + +private fun navigateToUpiSpendAnalysisScreen( + naviPayActivity: NaviPayActivity, + selectedMonth: SelectedMonth, +) { + val bundle = Bundle() + bundle.putParcelable(SELECTED_MONTH, selectedMonth) + DeepLinkManager.getDeepLinkListener() + ?.navigateTo( + activity = naviPayActivity, + ctaData = + CtaData( + url = "MONEY_MANAGER", + parameters = listOf(LineItem(key = "SOURCE", value = "NAVI_UPI")), + ), + bundle = bundle, + finish = false, + ) +} diff --git a/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/viewmodel/OrderHistoryViewModel.kt b/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/viewmodel/OrderHistoryViewModel.kt index bfe778154f..093817b8d5 100644 --- a/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/viewmodel/OrderHistoryViewModel.kt +++ b/android/navi-pay/src/main/kotlin/com/navi/pay/tstore/list/viewmodel/OrderHistoryViewModel.kt @@ -25,6 +25,11 @@ import com.navi.base.utils.EMPTY import com.navi.base.utils.ResourceProvider import com.navi.common.usecase.LitmusExperimentsUseCase import com.navi.common.utils.CommonUtils.getDisplayableAmount +import com.navi.moneymanager.common.datasync.UpiSpendDBSyncExecutor +import com.navi.moneymanager.common.datasync.model.DataSyncState +import com.navi.moneymanager.common.helper.UpiSpendAnalysisWidgetHelper +import com.navi.moneymanager.common.utils.Constants.CONTROL +import com.navi.moneymanager.common.utils.Constants.SPEND_ANALYSIS_EXP_CROSS_VARIANT import com.navi.pay.R import com.navi.pay.analytics.NaviPayAnalytics import com.navi.pay.analytics.NaviPayAnalytics.Companion.NAVI_PAY_T_STORE_ORDER_LIST @@ -42,7 +47,9 @@ import com.navi.pay.management.common.transaction.model.view.TransactionPaymentM import com.navi.pay.management.common.transaction.util.getMonthTagFromDateTime import com.navi.pay.management.transactionhistory.model.view.TransactionTagHolder import com.navi.pay.tstore.list.model.view.FilterTab +import com.navi.pay.tstore.list.model.view.OrderHistoryBottomsheetType import com.navi.pay.tstore.list.model.view.OrderHistoryEntity +import com.navi.pay.tstore.list.model.view.OrderHistoryScreenBottomSheetStateHolder import com.navi.pay.tstore.list.model.view.OrderHistorySearchHolder import com.navi.pay.tstore.list.model.view.OrderStatusOfView import com.navi.pay.tstore.list.repository.OrderRepository @@ -76,6 +83,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -96,6 +104,8 @@ constructor( private val litmusExperimentsUseCase: LitmusExperimentsUseCase, private val naviPayActivityDataProvider: NaviPayActivityDataProvider, private val naviPaySessionHelper: NaviPaySessionHelper, + private val upiDBSyncExecutor: UpiSpendDBSyncExecutor, + private val upiSpendAnalysisWidgetHelper: UpiSpendAnalysisWidgetHelper, ) : NaviPayBaseVM() { companion object { @@ -157,8 +167,21 @@ constructor( private val _monthTagsHolder = MutableStateFlow(emptyList()) val monthTagsHolder = _monthTagsHolder.asStateFlow() - private val _showBottomSheet = MutableStateFlow(false) - val showBottomSheet = _showBottomSheet.asStateFlow() + private val _bottomSheetStateHolder = + MutableStateFlow( + OrderHistoryScreenBottomSheetStateHolder( + showBottomSheet = false, + bottomSheetType = null, + ) + ) + val bottomSheetStateHolder = _bottomSheetStateHolder.asStateFlow() + + private val upiSpendAnalysisFirstSyncCompleted = MutableStateFlow(false) + + private val isUpiSpendAnalysisSyncCompleted = MutableStateFlow(false) + + private val _upiSpendAnalysisWidgetVariant = MutableStateFlow(CONTROL) + val upiSpendAnalysisWidgetVariant = _upiSpendAnalysisWidgetVariant.asStateFlow() val isFilterTagActive = combine( @@ -380,6 +403,42 @@ constructor( initialValue = 0, ) + @OptIn(ExperimentalCoroutinesApi::class) + val upiTxnHistorySpendAnalysisData = + combine( + isAnyTagActive, + searchQuery, + upiSpendAnalysisFirstSyncCompleted, + isUpiSpendAnalysisSyncCompleted, + orderRepository.getLatestOrderTimestamp().distinctUntilChanged(), + ) { + isAnyTagActive, + searchQuery, + upiSpendAnalysisFirstSyncCompleted, + upiSpendAnalysisSyncCompleted, + latestOrderTimestamp -> + // Display UPI Spend Analysis entrypoint when + // - No tag is selected. + // - The search query is empty. + // - Not in payee level transaction history mode. + // - UPI spend analysis data has been synced at least once. + val shouldShowEntrypoint = + (!isAnyTagActive && searchQuery.isBlank() && !isPayeeLevelTransactionHistory) && + (upiSpendAnalysisFirstSyncCompleted || upiSpendAnalysisSyncCompleted) + Pair(shouldShowEntrypoint, latestOrderTimestamp) + } + .flowOn(Dispatchers.IO) + .flatMapLatest { (shouldShowEntrypoint, latestOrderTimestamp) -> + flow { + upiSpendAnalysisWidgetHelper + .getTxnHistoryScreenSummaryData( + shouldShowEntrypoint = shouldShowEntrypoint, + latestOrderTimestamp = latestOrderTimestamp, + ) + ?.let { emit(it) } + } + } + init { GlobalScope.launch(Dispatchers.IO) { syncOrderHistoryUseCase.execute(screenName = screenName) @@ -396,6 +455,7 @@ constructor( } .collect() } + initUpiSpendAnalyser() initOrderSummaryAmountDataListener() triggerEventForShortcutWidget() } @@ -414,6 +474,46 @@ constructor( } } + private fun initUpiSpendAnalyser() { + syncUpiSpendAnalyserData() + fetchUpiSpenAnalyserWidgetVariant() + } + + private fun syncUpiSpendAnalyserData() { + viewModelScope.launch(Dispatchers.IO) { + if (upiSpendAnalysisWidgetHelper.isTotalSyncCompleted()) { + upiSpendAnalysisFirstSyncCompleted.update { true } + } + + val state = upiDBSyncExecutor.execute(screenName) + if (state is DataSyncState.Completed) { + isUpiSpendAnalysisSyncCompleted.update { true } + } + } + } + + private fun fetchUpiSpenAnalyserWidgetVariant() { + viewModelScope.launch(Dispatchers.IO) { + upiSpendAnalysisWidgetHelper.getEntryPointExpVariant().collect { variant -> + if ( + variant == SPEND_ANALYSIS_EXP_CROSS_VARIANT && + !upiSpendAnalysisWidgetHelper.shouldShowCrossVariant() + ) { + _upiSpendAnalysisWidgetVariant.update { CONTROL } + } else { + _upiSpendAnalysisWidgetVariant.update { variant } + } + } + } + } + + fun dismissUpiSpendAnalysisWidget() { + viewModelScope.launch(Dispatchers.IO) { + upiSpendAnalysisWidgetHelper.dismissUpiSpendAnalysisWidget() + _upiSpendAnalysisWidgetVariant.update { CONTROL } + } + } + fun refreshOrderHistory() { naviPayAnalytics.onTransactionHistoryRefreshed() viewModelScope.launch(Dispatchers.IO) { @@ -584,10 +684,6 @@ constructor( } } - fun updateBottomSheetState(showBottomSheet: Boolean) { - _showBottomSheet.update { showBottomSheet } - } - private fun getActiveTagHolder(tagHolders: List): List { return tagHolders .filter { it.isActive } @@ -1249,6 +1345,18 @@ constructor( fun getNaviPaySessionAttributes(): Map { return naviPaySessionHelper.getNaviPaySessionAttributes() } + + fun updateBottomSheetUiState( + showBottomSheet: Boolean, + bottomSheetType: OrderHistoryBottomsheetType? = null, + ) { + _bottomSheetStateHolder.update { + it.copy( + showBottomSheet = showBottomSheet, + bottomSheetType = bottomSheetType ?: it.bottomSheetType, + ) + } + } } enum class FilterTabBottomSheetUIState { diff --git a/android/navi-pay/src/main/kotlin/com/navi/pay/utils/NaviPayConstants.kt b/android/navi-pay/src/main/kotlin/com/navi/pay/utils/NaviPayConstants.kt index 9e527d6c1c..e67289a456 100644 --- a/android/navi-pay/src/main/kotlin/com/navi/pay/utils/NaviPayConstants.kt +++ b/android/navi-pay/src/main/kotlin/com/navi/pay/utils/NaviPayConstants.kt @@ -8,6 +8,7 @@ package com.navi.pay.utils import com.navi.common.utils.Constants.AUTO_READ_OTP_CONSENT_KEY +import com.navi.moneymanager.common.utils.Constants.LITMUS_EXPERIMENT_TXN_HISTORY_SPEND_ANALYSIS const val SOURCE_MODULE = "SOURCE_MODULE" const val BANK_LIST_IN_DB_REFRESH_MIN_TIMESTAMP = 86400000L // 1 day @@ -187,6 +188,7 @@ val NAVI_PAY_LITMUS_EXPERIMENTS = LITMUS_EXPERIMENT_NAVIPAY_OFFER_EXPERIENCE, LITMUS_EXPERIMENT_NAVIPAY_UPI_LITE_USP, LITMUS_EXPERIMENT_NAVIPAY_HIGHLIGHT_NEW_PAYEE, + LITMUS_EXPERIMENT_TXN_HISTORY_SPEND_ANALYSIS, ) // Generic diff --git a/android/navi-pay/src/main/res/values/strings.xml b/android/navi-pay/src/main/res/values/strings.xml index a72805c573..72942c9c87 100644 --- a/android/navi-pay/src/main/res/values/strings.xml +++ b/android/navi-pay/src/main/res/values/strings.xml @@ -853,4 +853,6 @@ Logout QR downloaded Set as wallpaper + This is your total debit amount for the month of %s, %s + \ No newline at end of file diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/extensions/ComposeWidgetExt.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/extensions/ComposeWidgetExt.kt index 4c6c77e635..e0fdc8e881 100644 --- a/android/navi-widgets/src/main/java/com/navi/naviwidgets/extensions/ComposeWidgetExt.kt +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/extensions/ComposeWidgetExt.kt @@ -1193,9 +1193,11 @@ fun CustomTooltip( peakHeightDp = tooltipShapeProperties?.peakHeight.orZero().dp, peakRadiusDp = tooltipShapeProperties?.peakRadius.orZero().dp, bottomPeakRadiusDp = tooltipShapeProperties?.bottomPeakRadius.orZero().dp, - defaultCornerRadiusDp = tooltipShapeProperties?.size.orZero().dp, + defaultCornerRadiusDp = + (tooltipShapeProperties?.size ?: properties?.background?.cornerRadius.orZero()).dp, peakDirection = tooltipShapeProperties?.peakDirection ?: PeakDirection.TOP, offsetPercentage = tooltipShapeProperties?.offsetPercentage.orZero(), + peakAspectRatio = properties?.background?.peakAspectRatio ?: ToolTipShape.RATIO, ) Column( modifier =