From a01d8e7c749cba1b936b7cb08ab0088d8825ec22 Mon Sep 17 00:00:00 2001 From: nikhil kumar Date: Fri, 10 Jan 2025 15:22:41 +0530 Subject: [PATCH] NTP-24432 | MM screens header animation (#14478) --- .../helper/TotalSpendSectionDataHelper.kt | 5 +- .../common/ui/composable/MMTopBar.kt | 34 ++- .../TopSectionForMonthAndBankSelection.kt | 135 +++++++++ .../moneymanager/common/utils/Constants.kt | 1 + .../model/CategoryDetailsScreenData.kt | 15 +- .../ui/CategoryDetailsScaffoldRenderer.kt | 263 +++++++++++------- .../model/SpendAnalysisScreenData.kt | 2 + .../ui/SpendAnalysisScaffoldRenderer.kt | 149 ++++++---- 8 files changed, 425 insertions(+), 179 deletions(-) create mode 100644 android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/sectionHeaders/TopSectionForMonthAndBankSelection.kt 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 71f5984768..cee47eedcd 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 @@ -25,7 +25,6 @@ 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.postonboard.categorydetails.model.CategoryTotalSpendSectionCategoryDataData -import com.navi.moneymanager.postonboard.categorydetails.model.CategoryTotalSpendSectionData import com.navi.moneymanager.postonboard.spendanalysis.model.TotalSpendSectionData import com.navi.naviwidgets.utils.NaviWidgetIconUtils.DOWN_ARROW_BLACK_16 import com.navi.naviwidgets.utils.NaviWidgetIconUtils.NEW_INFO_ICON @@ -67,10 +66,10 @@ constructor(@ApplicationContext private val context: Context) { availableBanks: List, selectedBankReferenceIds: Set?, categoryData: CategoryItemData?, - ): CategoryTotalSpendSectionData { + ): TotalSpendSectionData { val spendTransactions = getSpendTransactions(currentMonthTransactions) val sumOfTransactions = spendTransactions.sumOf { it.txnAmount.orZero() } - return CategoryTotalSpendSectionData( + return TotalSpendSectionData( iconUrl = IllustrationSource.Remote( url = categoryData?.categoryIcon ?: TOTAL_SPEND_RUPEE_SYMBOL, diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/MMTopBar.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/MMTopBar.kt index 0fe515f3be..3146b19d76 100644 --- a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/MMTopBar.kt +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/MMTopBar.kt @@ -8,6 +8,7 @@ package com.navi.moneymanager.common.ui.composable import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api @@ -35,6 +36,7 @@ fun MMTopBar( backgroundColor: Color = MMColor.white, roundedRipplePaddingValues: PaddingValues = PaddingValues(horizontal = 8.dp, vertical = 4.dp), title: String, + labelForTitle: String? = null, titleColor: Color = MMColor.textPrimary, titleMaxLines: Int = 1, navigationIcon: Int = R.drawable.ic_arrow_left_black_v2, @@ -45,16 +47,28 @@ fun MMTopBar( ) { CenterAlignedTopAppBar( title = { - MMText( - text = title, - modifier = Modifier.padding(horizontal = 40.dp), - color = titleColor, - fontSize = 14.sp, - fontWeight = FontWeightEnum.NAVI_BODY_REGULAR, - textAlign = TextAlign.Center, - overflow = TextOverflow.Ellipsis, - maxLines = titleMaxLines, - ) + Row(modifier = Modifier.padding(horizontal = 16.dp)) { + labelForTitle?.let { label -> + MMText( + text = "$label • ", + color = titleColor, + fontSize = 14.sp, + fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD, + textAlign = TextAlign.Center, + overflow = TextOverflow.Ellipsis, + maxLines = titleMaxLines, + ) + } + MMText( + text = title, + color = titleColor, + fontSize = 14.sp, + fontWeight = FontWeightEnum.NAVI_BODY_REGULAR, + textAlign = TextAlign.Center, + overflow = TextOverflow.Ellipsis, + maxLines = titleMaxLines, + ) + } }, navigationIcon = { IconButton(onClick = { onNavigationIconClick.invoke() }) { diff --git a/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/sectionHeaders/TopSectionForMonthAndBankSelection.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/sectionHeaders/TopSectionForMonthAndBankSelection.kt new file mode 100644 index 0000000000..73c4808e51 --- /dev/null +++ b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/common/ui/composable/sectionHeaders/TopSectionForMonthAndBankSelection.kt @@ -0,0 +1,135 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.common.ui.composable.sectionHeaders + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +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.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.VerticalDivider +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +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.moneymanager.common.illustration.model.IllustrationType +import com.navi.moneymanager.common.illustration.ui.Illustration +import com.navi.moneymanager.common.ui.composable.base.MMText +import com.navi.moneymanager.common.ui.theme.color.MMColor +import com.navi.moneymanager.common.utils.verticalShadow +import com.navi.moneymanager.postonboard.spendanalysis.model.TotalSpendSectionData + +@Composable +fun TopSectionForMonthAndBankSelection( + totalSpendSection: TotalSpendSectionData?, + onMonthChangeClick: (String) -> Unit, + onBankSelectionRequest: (Set) -> Unit, +) { + Column(modifier = Modifier.verticalShadow(showShadow = true, elevation = 3f)) { + HorizontalDivider( + modifier = Modifier.fillMaxWidth().height(1.dp), + color = MMColor.borderColor, + ) + + totalSpendSection?.let { totalSpendSection -> + TopScrolledSectionUI(totalSpendSection, onMonthChangeClick, onBankSelectionRequest) + } + } +} + +@Composable +private fun TopScrolledSectionUI( + data: TotalSpendSectionData, + onMonthChangeClick: (String) -> Unit, + onBankSelectionRequest: (Set) -> Unit, +) { + Row(modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Max)) { + Row( + modifier = + Modifier.onClickWithDebounce { onMonthChangeClick(data.selectedMonth) } + .weight(1f) + .wrapContentHeight() + .padding(12.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + MMText( + text = data.selectedMonth.uppercase(), + color = MMColor.textPrimary, + fontSize = 12.sp, + fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD, + lineHeight = 18.sp, + ) + Spacer(modifier = Modifier.width(6.dp)) + Box(Modifier.padding(bottom = 2.dp)) { + Illustration( + illustrationType = IllustrationType.Image(data.actionIcon), + modifier = Modifier.size(16.dp), + ) + } + } + + VerticalDivider( + modifier = Modifier.padding(vertical = 4.dp).fillMaxHeight().width(1.dp), + color = MMColor.borderColor, + ) + + Column(modifier = Modifier.weight(1f).wrapContentHeight()) { + data.spendingTrendSectionData?.let { headerModel -> + if (headerModel.actionText != null) { + Row( + modifier = + Modifier.onClickWithDebounce { + onBankSelectionRequest(data.selectedBankReferenceIds) + } + .fillMaxWidth() + .padding(12.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + headerModel.prefixIcon?.let { prefixIcon -> + Illustration( + illustrationType = IllustrationType.Image(prefixIcon), + modifier = Modifier.size(16.dp), + ) + } + Spacer(modifier = Modifier.width(6.dp)) + MMText( + text = headerModel.actionText, + color = MMColor.textPrimary, + fontSize = 12.sp, + fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD, + lineHeight = 18.sp, + ) + Spacer(modifier = Modifier.width(6.dp)) + headerModel.suffixIcon?.let { suffixIcon -> + Box(Modifier.padding(bottom = 2.dp)) { + Illustration( + illustrationType = IllustrationType.Image(suffixIcon), + modifier = Modifier.size(16.dp), + ) + } + } + } + } + } + } + } +} 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 29b1956ebf..786b4e9dc8 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 @@ -53,6 +53,7 @@ object Constants { const val REVOKE_CONSENT_FINARKEIN_URL = "https://revokeconsent.finvu.in/" const val CPS = "CPS" const val ALL_BANKS = "All banks" + const val HEADER_SCROLL_TWEEN = 20 // datastore constants const val IS_FIRST_MONTH_SYNC_COMPLETED = "IS_FIRST_MONTH_SYNC_COMPLETED" 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 0a43aba201..4f7fefd524 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,27 +11,16 @@ 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.common.model.sectionHeader.SectionHeaderData +import com.navi.moneymanager.postonboard.spendanalysis.model.TotalSpendSectionData data class CategoryDetailsScreenData( - val totalSpendSection: CategoryTotalSpendSectionData? = null, + val totalSpendSection: TotalSpendSectionData? = null, val barGraphData: BarGraphData? = null, val sortOption: SortOption = SortOption.RECENT_FIRST, val transactions: List? = null, val zeroTransactionData: ZeroTransactionData? = null, ) -data class CategoryTotalSpendSectionData( - val iconUrl: IllustrationSource, - val title: String, - val selectedMonth: String, - val actionIcon: IllustrationSource, - val amount: String, - val selectedBankReferenceIds: Set, - val spendingTrendSectionData: SectionHeaderData? = null, - val category: CategoryTotalSpendSectionCategoryDataData, -) - data class CategoryTotalSpendSectionCategoryDataData( val categoryId: String, val categoryName: String, 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 ec720ccf49..28e5a9fe04 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 @@ -7,6 +7,11 @@ package com.navi.moneymanager.postonboard.categorydetails.ui +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.EaseInOutQuart +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement @@ -29,6 +34,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Scaffold import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -38,7 +45,6 @@ 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.R as CommonR import com.navi.common.constants.HELP_CTA_TEXT import com.navi.common.utils.onClickWithDebounce import com.navi.design.font.FontWeightEnum @@ -57,15 +63,17 @@ import com.navi.moneymanager.common.ui.composable.MMTopBar import com.navi.moneymanager.common.ui.composable.barGraph.MMBarGraph import com.navi.moneymanager.common.ui.composable.base.MMText 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.transaction.Transaction import com.navi.moneymanager.common.ui.composable.transaction.TransactionDivider import com.navi.moneymanager.common.ui.theme.color.MMColor +import com.navi.moneymanager.common.utils.Constants.HEADER_SCROLL_TWEEN import com.navi.moneymanager.common.utils.roundedRippleClickable import com.navi.moneymanager.postonboard.categorydetails.model.CategoryDetailsScreenUiEffect import com.navi.moneymanager.postonboard.categorydetails.model.CategoryDetailsScreenUiState -import com.navi.moneymanager.postonboard.categorydetails.model.CategoryTotalSpendSectionData import com.navi.moneymanager.postonboard.categorydetails.model.SortOption import com.navi.moneymanager.postonboard.categorydetails.viewmodel.CategoryDetailsVM +import com.navi.moneymanager.postonboard.spendanalysis.model.TotalSpendSectionData @Composable fun CategoryDetailsScaffoldRenderer( @@ -80,134 +88,181 @@ fun CategoryDetailsScaffoldRenderer( onSortTransactionsRequest: () -> Unit, onBarGraphElementClicked: (SelectedMonth) -> Unit, ) { + + val rememberListState = rememberLazyListState() + val showScrolledStateTopSection by remember { + derivedStateOf { rememberListState.firstVisibleItemIndex > 0 } + } + Scaffold( modifier = modifier, containerColor = MMColor.white, topBar = { MMTopBar( - title = EMPTY, + labelForTitle = + if (showScrolledStateTopSection) { + categoryDetailsScreenUiState() + .screenData + ?.totalSpendSection + ?.category + ?.categoryName ?: EMPTY + } else { + null + }, + title = + if (showScrolledStateTopSection) { + categoryDetailsScreenUiState().screenData?.totalSpendSection?.amount + ?: EMPTY + } else { + EMPTY + }, titleColor = MMColor.ctaPrimary, - navigationIcon = CommonR.drawable.ic_arrow_left_black_v2, - actionIconText = HELP_CTA_TEXT, - onActionClick = { - CategoryDetailsEventTrackerImpl.onCategoryDetailsHelpClicked() - getViewModel().setEffect { CategoryDetailsScreenUiEffect.Navigation.Help } - }, + navigationIcon = com.navi.common.R.drawable.ic_arrow_left_black_v2, + actionIconText = if (!showScrolledStateTopSection) HELP_CTA_TEXT else null, + onActionClick = + if (!showScrolledStateTopSection) { + { + CategoryDetailsEventTrackerImpl.onCategoryDetailsHelpClicked() + getViewModel().setEffect { + CategoryDetailsScreenUiEffect.Navigation.Help + } + } + } else null, onNavigationIconClick = { getViewModel().setEffect { CategoryDetailsScreenUiEffect.Navigation.Back } }, ) }, ) { - LazyColumn( - modifier = Modifier.background(Color.White).padding(it).fillMaxHeight(), - state = rememberLazyListState(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - categoryDetailsScreenUiState().screenData?.totalSpendSection?.let { totalSpendSection -> - item { - CategoryTotalSpendSection( - totalSpendSection, - onMonthChangeClick, - onBankSelectionRequest, - onCategoryClick, - onInfoButtonClick = onInfoButtonClick, - ) - } - } - categoryDetailsScreenUiState().screenData?.barGraphData?.let { barGraphData -> - item { - MMBarGraph( - screenName = MMScreen.CATEGORY_DETAILS.screen, - barGraphData = barGraphData, - onAverageInfoClick = onAverageInfoClick, - onBarGraphElementClicked = onBarGraphElementClicked, - ) - } - } - item { Spacer(modifier = Modifier.height(16.dp)) } - val transactions = categoryDetailsScreenUiState().screenData?.transactions - if (transactions.isNullOrEmpty()) { - categoryDetailsScreenUiState().screenData?.zeroTransactionData?.let { - zeroTransactionData -> + Box { + LazyColumn( + modifier = Modifier.background(Color.White).padding(it).fillMaxHeight(), + state = rememberListState, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + categoryDetailsScreenUiState().screenData?.totalSpendSection?.let { + totalSpendSection -> item { - SectionHeader( - headerModel = SectionHeaderData(title = "Category transactions"), - isActionVisible = false, - rippleStartPadding = 0.dp, - ) - ZeroTransactionView( - title = zeroTransactionData.title, - illustrationSource = zeroTransactionData.illustrationSource, - modifier = Modifier.fillMaxWidth().padding(vertical = 32.dp), + CategoryTotalSpendSection( + totalSpendSection, + onMonthChangeClick, + onBankSelectionRequest, + onCategoryClick, + onInfoButtonClick = onInfoButtonClick, ) } } - } else { - categoryDetailsScreenUiState().screenData?.sortOption?.let { currentSortOption -> + categoryDetailsScreenUiState().screenData?.barGraphData?.let { barGraphData -> item { - SectionHeader( - headerModel = - SectionHeaderData( - title = "Category transactions", - actionText = - stringResource( - when (currentSortOption) { - SortOption.HIGHEST_FIRST -> R.string.highest_first - SortOption.LOWEST_FIRST -> R.string.lowest_first - SortOption.RECENT_FIRST -> R.string.recent_first - } - ), - suffixIcon = - IllustrationSource.Remote( - url = SORT_ICON, - placeholder = IMAGE_TRANSPARENT_PLACEHOLDER_SMALL, - ), - ), - onAction = onSortTransactionsRequest, - isActionVisible = true, - rippleStartPadding = 12.dp, + MMBarGraph( + screenName = MMScreen.CATEGORY_DETAILS.screen, + barGraphData = barGraphData, + onAverageInfoClick = onAverageInfoClick, + onBarGraphElementClicked = onBarGraphElementClicked, ) } } - - itemsIndexed(transactions) { index, transaction -> - Transaction( - transaction = transaction, - onClick = { transactionId -> - CategoryDetailsEventTrackerImpl.onCategoryDetailsTransactionClicked( - transactionRank = index + 1 + item { Spacer(modifier = Modifier.height(16.dp)) } + val transactions = categoryDetailsScreenUiState().screenData?.transactions + if (transactions.isNullOrEmpty()) { + categoryDetailsScreenUiState().screenData?.zeroTransactionData?.let { + zeroTransactionData -> + item { + SectionHeader( + headerModel = SectionHeaderData(title = "Category transactions"), + isActionVisible = false, + rippleStartPadding = 0.dp, ) - getViewModel().setEffect { - CategoryDetailsScreenUiEffect.Navigation.TransactionDetails( - transactionId = transactionId - ) - } - }, - onCategoryClick = { txn -> - CategoryDetailsEventTrackerImpl - .onCategoryDetailsTransactionCategoryClicked( - transactionRank = index + 1, - category = txn.categoryId, - ) - getViewModel().setEffect { - CategoryDetailsScreenUiEffect.OpenCategoryBottomSheet(txn) - } - }, - ) + ZeroTransactionView( + title = zeroTransactionData.title, + illustrationSource = zeroTransactionData.illustrationSource, + modifier = Modifier.fillMaxWidth().padding(vertical = 32.dp), + ) + } + } + } else { + categoryDetailsScreenUiState().screenData?.sortOption?.let { currentSortOption + -> + item { + SectionHeader( + headerModel = + SectionHeaderData( + title = "Category transactions", + actionText = + stringResource( + when (currentSortOption) { + SortOption.HIGHEST_FIRST -> + R.string.highest_first + SortOption.LOWEST_FIRST -> R.string.lowest_first + SortOption.RECENT_FIRST -> R.string.recent_first + } + ), + suffixIcon = + IllustrationSource.Remote( + url = SORT_ICON, + placeholder = IMAGE_TRANSPARENT_PLACEHOLDER_SMALL, + ), + ), + onAction = onSortTransactionsRequest, + isActionVisible = true, + rippleStartPadding = 12.dp, + ) + } + } - if (index < transactions.lastIndex) { - TransactionDivider() + itemsIndexed(transactions) { index, transaction -> + Transaction( + transaction = transaction, + onClick = { transactionId -> + CategoryDetailsEventTrackerImpl.onCategoryDetailsTransactionClicked( + transactionRank = index + 1 + ) + getViewModel().setEffect { + CategoryDetailsScreenUiEffect.Navigation.TransactionDetails( + transactionId = transactionId + ) + } + }, + onCategoryClick = { txn -> + CategoryDetailsEventTrackerImpl + .onCategoryDetailsTransactionCategoryClicked( + transactionRank = index + 1, + category = txn.categoryId, + ) + getViewModel().setEffect { + CategoryDetailsScreenUiEffect.OpenCategoryBottomSheet(txn) + } + }, + ) + + if (index < transactions.lastIndex) { + TransactionDivider() + } } } } + + AnimatedVisibility( + modifier = Modifier.background(Color.White).padding(top = it.calculateTopPadding()), + visible = showScrolledStateTopSection, + enter = + fadeIn(tween(durationMillis = HEADER_SCROLL_TWEEN, easing = EaseInOutQuart)), + exit = fadeOut(tween(durationMillis = HEADER_SCROLL_TWEEN, easing = EaseInOutQuart)), + ) { + TopSectionForMonthAndBankSelection( + totalSpendSection = + categoryDetailsScreenUiState().screenData?.totalSpendSection, + onMonthChangeClick = onMonthChangeClick, + onBankSelectionRequest = onBankSelectionRequest, + ) + } } } } @Composable private fun CategoryTotalSpendSection( - data: CategoryTotalSpendSectionData, + data: TotalSpendSectionData, onMonthChangeClick: (String) -> Unit, onBankSelectionRequest: (Set) -> Unit, onCategoryClick: (String) -> Unit, @@ -313,7 +368,7 @@ private fun CategoryTotalSpendSection( @Composable private fun CategoryRow( - data: CategoryTotalSpendSectionData, + data: TotalSpendSectionData, onCategoryClick: (String) -> Unit, onInfoButtonClick: () -> Unit, ) { @@ -325,12 +380,14 @@ private fun CategoryRow( modifier = Modifier.background(color = MMColor.ctaSecondary, shape = RoundedCornerShape(32.dp)) .clip(RoundedCornerShape(32.dp)) - .onClickWithDebounce { onCategoryClick(data.category.categoryId) } + .onClickWithDebounce { + data.category?.categoryId?.let { categoryId -> onCategoryClick(categoryId) } + } .padding(horizontal = 8.dp, vertical = 4.dp), verticalAlignment = Alignment.CenterVertically, ) { MMText( - text = data.category.categoryName, + text = data.category?.categoryName ?: EMPTY, modifier = Modifier.padding(horizontal = 4.dp), color = MMColor.textPrimary, fontSize = 14.sp, @@ -341,7 +398,7 @@ private fun CategoryRow( modifier = Modifier.size(16.dp), ) } - data.category.infoIconUrl?.let { + data.category?.infoIconUrl?.let { Spacer(Modifier.width(8.dp)) Illustration( illustrationType = IllustrationType.Image(it), 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 2c5675d2d6..eba4c50646 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 @@ -11,6 +11,7 @@ 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.sectionHeader.SectionHeaderData +import com.navi.moneymanager.postonboard.categorydetails.model.CategoryTotalSpendSectionCategoryDataData import com.navi.moneymanager.postonboard.dashboard.model.NavBarData data class SpendAnalysisScreenData( @@ -30,4 +31,5 @@ data class TotalSpendSectionData( val amount: String, 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/ui/SpendAnalysisScaffoldRenderer.kt b/android/navi-money-manager/src/main/kotlin/com/navi/moneymanager/postonboard/spendanalysis/ui/SpendAnalysisScaffoldRenderer.kt index a791d2a99d..ff721f35f5 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 @@ -7,25 +7,32 @@ package com.navi.moneymanager.postonboard.spendanalysis.ui +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.EaseInOutQuart +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import com.navi.base.utils.EMPTY -import com.navi.common.R as CommonR +import com.navi.common.R import com.navi.common.constants.HELP_CTA_TEXT import com.navi.moneymanager.common.analytics.SpendAnalysisEventTrackerImpl import com.navi.moneymanager.common.illustration.model.IllustrationSource @@ -41,8 +48,10 @@ import com.navi.moneymanager.common.ui.composable.MMTopBar import com.navi.moneymanager.common.ui.composable.TotalSpendSectionUI import com.navi.moneymanager.common.ui.composable.barGraph.MMBarGraph import com.navi.moneymanager.common.ui.composable.button.PrimaryButton +import com.navi.moneymanager.common.ui.composable.sectionHeaders.TopSectionForMonthAndBankSelection import com.navi.moneymanager.common.ui.composable.spendCategoriztion.SpendCategorizationSection import com.navi.moneymanager.common.ui.theme.color.MMColor +import com.navi.moneymanager.common.utils.Constants.HEADER_SCROLL_TWEEN import com.navi.moneymanager.postonboard.spendanalysis.model.SpendAnalysisScreenUiEffect import com.navi.moneymanager.postonboard.spendanalysis.model.SpendAnalysisScreenUiState import com.navi.moneymanager.postonboard.spendanalysis.viewmodel.SpendAnalysisVM @@ -59,76 +68,116 @@ fun SpendAnalysisScaffoldRenderer( onViewAllTransactionsClick: () -> Unit, onBarGraphElementClicked: (SelectedMonth) -> Unit, ) { + + val rememberListState = rememberLazyListState() + val showScrolledStateTopSection by remember { + derivedStateOf { rememberListState.firstVisibleItemIndex > 0 } + } + Scaffold( modifier = modifier, containerColor = MMColor.white, topBar = { MMTopBar( - title = EMPTY, + labelForTitle = + if (showScrolledStateTopSection) { + spendAnalysisScreenUiState().screenData?.totalSpendSection?.title ?: EMPTY + } else { + null + }, + title = + if (showScrolledStateTopSection) { + spendAnalysisScreenUiState().screenData?.totalSpendSection?.amount ?: EMPTY + } else { + EMPTY + }, titleColor = MMColor.ctaPrimary, - navigationIcon = CommonR.drawable.ic_arrow_left_black_v2, - actionIconText = HELP_CTA_TEXT, - onActionClick = { - SpendAnalysisEventTrackerImpl.onSpendAnalysisScreenHelpClicked() - getViewModel().setEffect { SpendAnalysisScreenUiEffect.Navigation.Help } - }, + navigationIcon = R.drawable.ic_arrow_left_black_v2, + actionIconText = if (!showScrolledStateTopSection) HELP_CTA_TEXT else null, + onActionClick = + if (!showScrolledStateTopSection) { + { + SpendAnalysisEventTrackerImpl.onSpendAnalysisScreenHelpClicked() + getViewModel().setEffect { SpendAnalysisScreenUiEffect.Navigation.Help } + } + } else null, onNavigationIconClick = { getViewModel().setEffect { SpendAnalysisScreenUiEffect.Navigation.Back } }, ) }, ) { - Column( - modifier = - Modifier.background(Color.White) - .padding(it) - .fillMaxHeight() - .verticalScroll(rememberScrollState()), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.SpaceBetween, - ) { - Column { + Box { + LazyColumn( + modifier = Modifier.background(Color.White).padding(it).fillMaxHeight(), + state = rememberListState, + horizontalAlignment = Alignment.CenterHorizontally, + ) { spendAnalysisScreenUiState().screenData?.totalSpendSection?.let { totalSpendSection -> - TotalSpendSectionUI( - totalSpendSection, - onMonthChangeClick, - onBankSelectionRequest, - ) + item { + TotalSpendSectionUI( + totalSpendSection, + onMonthChangeClick, + onBankSelectionRequest, + ) + } } + spendAnalysisScreenUiState().screenData?.barGraphData?.let { barGraphData -> - MMBarGraph( - MMScreen.SPEND_ANALYSIS.screen, - barGraphData, - onAverageInfoClick, - onBarGraphElementClicked = { selectedMonth -> - onBarGraphElementClicked(selectedMonth) - }, - ) + item { + MMBarGraph( + MMScreen.SPEND_ANALYSIS.screen, + barGraphData, + onAverageInfoClick, + onBarGraphElementClicked = { selectedMonth -> + onBarGraphElementClicked(selectedMonth) + }, + ) + } } + + item { Spacer(modifier = Modifier.height(16.dp)) } + spendAnalysisScreenUiState().screenData?.spendCategorizationState?.let { spendCategorizationState -> - Spacer(modifier = Modifier.height(16.dp)) - SpendCategorizationSection( - screenName = MMScreen.SPEND_ANALYSIS.screen, - isAddAccountSelected = false, - spendCategorizationState = spendCategorizationState, - spendCategorizationAction = { action -> - when (action) { - is SpendCategorizationAction.SelectCategory -> { - onCategoryClick(action.categoryId) + item { + SpendCategorizationSection( + screenName = MMScreen.SPEND_ANALYSIS.screen, + isAddAccountSelected = false, + spendCategorizationState = spendCategorizationState, + spendCategorizationAction = { action -> + when (action) { + is SpendCategorizationAction.SelectCategory -> { + onCategoryClick(action.categoryId) + } + else -> {} } - else -> {} - } - }, - ) + }, + ) + } } spendAnalysisScreenUiState().screenData?.viewTransactionHistoryTitle?.let { title -> - PrimaryButton(title = title, onClick = onViewAllTransactionsClick) + item { PrimaryButton(title = title, onClick = onViewAllTransactionsClick) } } + + item { SpendAnalysisFooterSection() } + } + + AnimatedVisibility( + modifier = Modifier.background(Color.White).padding(top = it.calculateTopPadding()), + visible = showScrolledStateTopSection, + enter = + fadeIn(tween(durationMillis = HEADER_SCROLL_TWEEN, easing = EaseInOutQuart)), + exit = fadeOut(tween(durationMillis = HEADER_SCROLL_TWEEN, easing = EaseInOutQuart)), + ) { + TopSectionForMonthAndBankSelection( + totalSpendSection = spendAnalysisScreenUiState().screenData?.totalSpendSection, + onMonthChangeClick = onMonthChangeClick, + onBankSelectionRequest = onBankSelectionRequest, + ) } - SpendAnalysisFooterSection() } } }