Skip to content
This repository has been archived by the owner on Nov 5, 2024. It is now read-only.

Fix issue 3364 #3431

Merged
merged 10 commits into from
Aug 27, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import androidx.annotation.DrawableRes
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraintsScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
Expand All @@ -14,12 +16,14 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
Expand Down Expand Up @@ -47,6 +51,9 @@ import com.ivy.data.model.primitive.IconAsset
import com.ivy.data.model.primitive.NotBlankTrimmedString
import com.ivy.design.l0_system.UI
import com.ivy.design.l0_system.style
import com.ivy.legacy.utils.balancePrefix
import com.ivy.legacy.utils.compactBalancePrefix
import com.ivy.legacy.utils.format
import com.ivy.navigation.CategoriesScreen
import com.ivy.navigation.TransactionsScreen
import com.ivy.navigation.navigation
Expand Down Expand Up @@ -93,7 +100,7 @@ fun BoxWithConstraintsScope.CategoriesScreen(screen: CategoriesScreen) {

@Composable
private fun BoxWithConstraintsScope.UI(
state: CategoriesScreenState = CategoriesScreenState(),
state: CategoriesScreenState = CategoriesScreenState(compactCategoriesModeEnabled = false),
onEvent: (CategoriesScreenEvent) -> Unit = {}
) {
val nav = navigation()
Expand Down Expand Up @@ -155,10 +162,10 @@ private fun BoxWithConstraintsScope.UI(
}

items(state.categories, key = { it.category.id.value }) { categoryData ->
Spacer(Modifier.height(16.dp))
CategoryCard(
currency = state.baseCurrency,
categoryData = categoryData,
compactModeEnabled = state.compactCategoriesModeEnabled,
onLongClick = {
onEvent(CategoriesScreenEvent.OnReorderModalVisible(true))
}
Expand Down Expand Up @@ -240,8 +247,31 @@ private fun BoxWithConstraintsScope.UI(
private fun CategoryCard(
currency: String,
categoryData: CategoryData,
compactModeEnabled: Boolean,
onLongClick: () -> Unit,
onClick: () -> Unit
) {
val contrastColor = findContrastTextColor(categoryData.category.color.value.toComposeColor())

if (!compactModeEnabled) {
Spacer(Modifier.height(16.dp))
DefaultCategoryCard(onClick, categoryData, currency)
} else {
Spacer(Modifier.height(8.dp))
CompactCategoryCard(
categoryData = categoryData,
contrastColor = contrastColor,
currency = currency,
onClick = onClick
)
}
}

@Composable
private fun DefaultCategoryCard(
onClick: () -> Unit,
categoryData: CategoryData,
currency: String
) {
Column(
modifier = Modifier
Expand Down Expand Up @@ -272,6 +302,92 @@ private fun CategoryCard(
}
}

@Composable
private fun CompactCategoryCard(
categoryData: CategoryData,
contrastColor: Color,
currency: String,
onClick: () -> Unit
) {
val category = categoryData.category
val balancePrefixValue = compactBalancePrefix(
income = categoryData.monthlyIncome,
expenses = categoryData.monthlyExpenses
)

Box(
modifier = Modifier
.padding(horizontal = 16.dp)
.border(2.dp, UI.colors.medium, UI.shapes.r4)
.clickable(
onClick = onClick
),
) {
Row(
modifier = Modifier
.padding(all = 10.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Box(
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.background(category.color.value.toComposeColor()),
contentAlignment = Alignment.Center,
) {
ItemIconSDefaultIcon(
iconName = category.icon?.id,
defaultIcon = R.drawable.ic_custom_account_s,
tint = contrastColor
)
}

Row(
modifier =
Modifier
.padding(horizontal = 8.dp)
.fillMaxWidth()
.fillMaxHeight(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = category.name.value,
style = UI.typo.b2.style(
fontWeight = FontWeight.Bold
)
)

Row(
verticalAlignment = Alignment.CenterVertically
) {
// Format the monthly balance according to the currency format and remove
// any '+' or '-' signs that might be included from the prefix to ensure
// a clean and consistent representation.
val currencyFormatted =
categoryData.monthlyBalance.format(currency).replace(Regex("[+-]"), "")

Text(
text = "$balancePrefixValue$currencyFormatted",
style = UI.typo.nB1.style(
color = UI.colors.pureInverse,
fontWeight = FontWeight.Bold
)
)
Spacer(modifier = Modifier.width(4.dp))
Text(
text = currency,
style = UI.typo.nB2.style(
color = UI.colors.pureInverse,
fontWeight = FontWeight.Medium
)
)
}
}
}
}
}

@Composable
fun AddedSpent(
monthlyIncome: Double,
Expand Down Expand Up @@ -378,6 +494,10 @@ private fun CategoryHeader(
contrastColor: Color,
) {
val category = categoryData.category
val balancePrefixValue = balancePrefix(
income = categoryData.monthlyIncome,
expenses = categoryData.monthlyExpenses
)

Column(
modifier = Modifier
Expand Down Expand Up @@ -421,10 +541,7 @@ private fun CategoryHeader(
currencyFontSize = 30.sp,

currencyUpfront = false,
balanceAmountPrefix = com.ivy.legacy.utils.balancePrefix(
income = categoryData.monthlyIncome,
expenses = categoryData.monthlyExpenses
)
balanceAmountPrefix = balancePrefixValue
)

Spacer(Modifier.height(16.dp))
Expand Down Expand Up @@ -556,10 +673,17 @@ private fun SelectTypeButton(

@Preview
@Composable
private fun Preview(theme: Theme = Theme.LIGHT) {
private fun PreviewCategoriesCompactModeEnabled(theme: Theme = Theme.LIGHT) {
ILIYANGERMANOV marked this conversation as resolved.
Show resolved Hide resolved
Preview(theme = theme, compactModeEnabled = true)
}

@Preview
@Composable
private fun Preview(theme: Theme = Theme.LIGHT, compactModeEnabled: Boolean = false) {
com.ivy.legacy.IvyWalletPreview(theme) {
val state = CategoriesScreenState(
baseCurrency = "BGN",
compactCategoriesModeEnabled = compactModeEnabled,
categories = persistentListOf(
CategoryData(
category = Category(
Expand Down Expand Up @@ -637,3 +761,13 @@ fun CategoriesScreenUiTest(isDark: Boolean) {
}
Preview(theme)
}

/** For screenshot testing */
@Composable
fun CategoriesScreenCompactUiTest(isDark: Boolean) {
val theme = when (isDark) {
true -> Theme.DARK
false -> Theme.LIGHT
}
Preview(theme, compactModeEnabled = true)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ data class CategoriesScreenState(
val categoryModalData: CategoryModalData? = null,
val sortModalVisible: Boolean = false,
val sortOrderItems: ImmutableList<SortOrder> = SortOrder.values().toList().toImmutableList(),
val sortOrder: SortOrder = SortOrder.DEFAULT
val sortOrder: SortOrder = SortOrder.DEFAULT,
val compactCategoriesModeEnabled: Boolean,
)
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.ivy.base.legacy.SharedPrefs
import com.ivy.base.legacy.Transaction
import com.ivy.ui.ComposeViewModel
import com.ivy.data.repository.CategoryRepository
import com.ivy.domain.features.Features
import com.ivy.frp.action.thenMap
import com.ivy.frp.thenInvokeAfter
import com.ivy.legacy.data.model.TimePeriod
Expand Down Expand Up @@ -44,6 +45,7 @@ class CategoriesViewModel @Inject constructor(
private val accountsAct: AccountsAct,
private val trnsWithRangeAndAccFiltersAct: TrnsWithRangeAndAccFiltersAct,
private val categoryIncomeWithAccountFiltersAct: LegacyCategoryIncomeWithAccountFiltersAct,
private val features: Features,
) : ComposeViewModel<CategoriesScreenState, CategoriesScreenEvent>() {

private val baseCurrency = mutableStateOf("")
Expand All @@ -66,10 +68,16 @@ class CategoriesViewModel @Inject constructor(
reorderModalVisible = getReorderModalVisible(),
categoryModalData = getCategoryModalData(),
sortOrder = getSortOrder(),
sortModalVisible = getSortModalVisible()
sortModalVisible = getSortModalVisible(),
compactCategoriesModeEnabled = getCompactCategoriesMode(),
)
}

@Composable
private fun getCompactCategoriesMode(): Boolean {
return features.compactCategoriesMode.asEnabledState()
}

@Composable
private fun getBaseCurrency(): String {
return baseCurrency.value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,16 @@ class CategoriesScreenPaparazziTest(
private val theme: PaparazziTheme,
) : PaparazziScreenshotTest() {
@Test
fun `snapshot Categories Screen`() {
fun `snapshot Categories nonCompact Screen`() {
snapshot(theme) {
CategoriesScreenUiTest(theme == PaparazziTheme.Dark)
}
}

@Test
fun `snapshot Categories compact Screen`() {
snapshot(theme) {
CategoriesScreenCompactUiTest(theme == PaparazziTheme.Dark)
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.ivy.domain.features
interface Features {
val sortCategoriesAlphabetically: BoolFeature
val compactAccountsMode: BoolFeature
val compactCategoriesMode: BoolFeature

val allFeatures: List<BoolFeature>
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,30 @@ import javax.inject.Singleton

@Singleton
class IvyFeatures @Inject constructor() : Features {

override val sortCategoriesAlphabetically = BoolFeature(
key = "sort_categories_alphabetically",
name = "Sort Categories Alphabetically",
description = "Sort income and expenses" +
" categories alphabetically"
)

override val compactAccountsMode = BoolFeature(
key = "compact_account_ui",
name = "Compact account UI",
description = "Enables more compact and dense UI for the \"Accounts\" tab"
)

override val compactCategoriesMode = BoolFeature(
key = "compact_categories_ui",
name = "Compact category UI",
description = "Activates a more streamlined and space-efficient interface for the \"Categories\" Screen"
)

override val allFeatures: List<BoolFeature>
get() = listOf(
sortCategoriesAlphabetically,
compactAccountsMode
compactAccountsMode,
compactCategoriesMode
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,27 @@ fun balancePrefix(
expenses != 0.0 && income != 0.0 -> {
null
}

expenses < 0.0 && income == 0.0 -> {
"-"
}

income > 0.0 && expenses == 0.0 -> {
"+"
}

else -> null
}
}

fun compactBalancePrefix(
income: Double = 0.0,
expenses: Double = 0.0
): String {
val balance = income - expenses
return when {
balance > 0 -> "+"
balance < 0 -> "-"
else -> ""
}
}