NTP-24432 | MM screens header animation (#14478)
This commit is contained in:
@@ -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<AccountOverview>,
|
||||
selectedBankReferenceIds: Set<String>?,
|
||||
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,
|
||||
|
||||
@@ -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() }) {
|
||||
|
||||
@@ -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<String>) -> 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<String>) -> 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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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<Transaction>? = 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<String>,
|
||||
val spendingTrendSectionData: SectionHeaderData? = null,
|
||||
val category: CategoryTotalSpendSectionCategoryDataData,
|
||||
)
|
||||
|
||||
data class CategoryTotalSpendSectionCategoryDataData(
|
||||
val categoryId: String,
|
||||
val categoryName: String,
|
||||
|
||||
@@ -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<String>) -> 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),
|
||||
|
||||
@@ -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<String>,
|
||||
val spendingTrendSectionData: SectionHeaderData? = null,
|
||||
val category: CategoryTotalSpendSectionCategoryDataData? = null,
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user