Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
Expand Down Expand Up @@ -65,6 +67,7 @@ import com.resolum.intiva.features.finances.presentation.spendinglimits.Spending
import com.resolum.intiva.features.finances.presentation.spendinglimits.components.SpendingLimitCard
import com.resolum.intiva.features.finances.presentation.transactions.TransactionViewModel
import com.resolum.intiva.features.finances.presentation.transactions.components.TransactionItem
import com.resolum.intiva.features.paymentmethodsandcategories.presentation.category.CategoryViewModel
import com.resolum.intiva.features.profiles.domain.models.FirstTransactionTutorialStep
import com.resolum.intiva.features.profiles.presentation.onboarding.OnboardingViewModel
import com.resolum.intiva.features.profiles.presentation.onboarding.components.FirstTransactionPresentationOverlay
Expand Down Expand Up @@ -97,6 +100,7 @@ fun HomeScreen(
navController: NavController,
viewModel: TransactionViewModel = hiltViewModel(),
spendingLimitViewModel: SpendingLimitViewModel = hiltViewModel(),
categoryViewModel: CategoryViewModel = hiltViewModel(),
profileViewModel: ProfileViewModel = hiltViewModel(),
onNavigateToTransactions: () -> Unit,
onNavigateToSpendingLimitAlert: () -> Unit = {},
Expand All @@ -105,6 +109,7 @@ fun HomeScreen(
val snackBarHostState = remember { SnackbarHostState() }
val uiState by viewModel.uiState.collectAsState()
val spendingLimitUiState by spendingLimitViewModel.uiState.collectAsState()
val categoryUiState by categoryViewModel.uiState.collectAsState()
val profileUiState by profileViewModel.uiState.collectAsState()
val onboardingViewModel: OnboardingViewModel = hiltViewModel()
val onboardingState by onboardingViewModel.state.collectAsState()
Expand All @@ -116,6 +121,11 @@ fun HomeScreen(
onboardingViewModel.loadStatus()
viewModel.getTransactionsByOwnerId(onlyLatest = true)
spendingLimitViewModel.loadMonthlySpendingLimit()
spendingLimitViewModel.loadSpendingLimits()
categoryViewModel.getCategories(
ownerType = com.resolum.intiva.features.shared.domain.model.OwnerType.INDIVIDUAL.name,
type = com.resolum.intiva.features.finances.domain.models.TransactionType.EXPENSE.name
)
profileViewModel.loadProfile()

val success = navController.currentBackStackEntry
Expand Down Expand Up @@ -323,11 +333,52 @@ fun HomeScreen(
}

item {
SpendingLimitCard(
state = spendingLimitUiState.spendingLimitState,
onRetry = { spendingLimitViewModel.loadMonthlySpendingLimit() },
onOpenAlert = onNavigateToSpendingLimitAlert
)
when (val state = spendingLimitUiState.spendingLimitsState) {
is UiState.Success -> {
if (state.data.isEmpty()) {
SpendingLimitCard(
state = UiState.Idle,
onRetry = { spendingLimitViewModel.loadSpendingLimits() },
onOpenAlert = onNavigateToSpendingLimitAlert
)
} else {
LazyRow(
horizontalArrangement = Arrangement.spacedBy(12.dp),
contentPadding = PaddingValues(horizontal = 0.dp)
) {
items(state.data) { summary ->
SpendingLimitCard(
state = UiState.Success(summary),
category = categoryUiState.categories.firstOrNull {
it.id == summary.limit.targetId
},
onRetry = { spendingLimitViewModel.loadSpendingLimits() },
onOpenAlert = onNavigateToSpendingLimitAlert,
modifier = Modifier.fillParentMaxWidth(0.92f)
)
}
}
}
}

is UiState.Loading -> SpendingLimitCard(
state = UiState.Loading,
onRetry = { spendingLimitViewModel.loadSpendingLimits() },
onOpenAlert = onNavigateToSpendingLimitAlert
)

is UiState.Error -> SpendingLimitCard(
state = UiState.Error(message = state.message, throwable = state.throwable),
onRetry = { spendingLimitViewModel.loadSpendingLimits() },
onOpenAlert = onNavigateToSpendingLimitAlert
)

is UiState.Idle -> SpendingLimitCard(
state = spendingLimitUiState.spendingLimitState,
onRetry = { spendingLimitViewModel.loadSpendingLimits() },
onOpenAlert = onNavigateToSpendingLimitAlert
)
}
}

item {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.resolum.intiva.features.finances.presentation.spendinglimits
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
Expand All @@ -12,12 +13,14 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
Expand All @@ -29,6 +32,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.resolum.intiva.core.common.state.UiState
import com.resolum.intiva.core.ui.theme.IntivaColors
import com.resolum.intiva.features.finances.domain.models.SpendingLimit
import com.resolum.intiva.features.finances.presentation.spendinglimits.components.AdjustSpendingLimitSheet
import com.resolum.intiva.features.finances.presentation.spendinglimits.components.BudgetSummaryCard
import com.resolum.intiva.features.finances.presentation.spendinglimits.components.EmptyLimitsCard
import com.resolum.intiva.features.finances.presentation.spendinglimits.components.MessageCard
Expand All @@ -41,15 +46,20 @@ fun SpendingLimitListContent(
limitsState: UiState<List<SpendingLimitSummary>>,
categories: List<Category>,
onAddClick: () -> Unit,
updateState: UiState<SpendingLimit>,
selectedLimitToAdjust: SpendingLimitSummary?,
onAdjustClick: (SpendingLimitSummary) -> Unit,
onDismissAdjust: () -> Unit,
onSaveAdjust: (SpendingLimit, String, SpendingLimitFrequency, Boolean) -> Unit,
onBack: () -> Unit
) {
Scaffold(
containerColor = Color(0xFFFAF7FF),
containerColor = IntivaColors.BackgroundDefault,
floatingActionButton = {
FloatingActionButton(
onClick = onAddClick,
containerColor = IntivaColors.PrimaryGreen,
contentColor = IntivaColors.TextPrimary,
containerColor = IntivaColors.PrimaryBrand,
contentColor = IntivaColors.TextInverse,
shape = CircleShape
) {
Icon(
Expand All @@ -65,16 +75,28 @@ fun SpendingLimitListContent(
.fillMaxSize()
.padding(padding)
.padding(horizontal = 24.dp),
contentPadding = PaddingValues(top = 32.dp, bottom = 112.dp),
contentPadding = PaddingValues(top = 18.dp, bottom = 112.dp),
verticalArrangement = Arrangement.spacedBy(18.dp)
) {
item {
Text(
text = "Límites de Gasto",
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
color = IntivaColors.TextPrimary
)
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
IconButton(onClick = onBack) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Volver",
tint = IntivaColors.TextPrimary
)
}
Text(
text = "Límites de Gasto",
fontSize = 30.sp,
fontWeight = FontWeight.Bold,
color = IntivaColors.TextPrimary
)
}
Spacer(modifier = Modifier.height(6.dp))
Text(
text = "Monitorea tus presupuestos por categoría este mes.",
Expand Down Expand Up @@ -109,7 +131,8 @@ fun SpendingLimitListContent(
items(summaries) { summary ->
SpendingLimitListItem(
summary = summary,
category = categories.firstOrNull { it.id == summary.limit.targetId }
category = categories.firstOrNull { it.id == summary.limit.targetId },
onAdjustClick = { onAdjustClick(summary) }
)
}
}
Expand All @@ -135,4 +158,15 @@ fun SpendingLimitListContent(
}
}
}

selectedLimitToAdjust?.let { summary ->
AdjustSpendingLimitSheet(
limit = summary.limit,
updateState = updateState,
onDismiss = onDismissAdjust,
onSave = { amount, frequency, updatePeriod ->
onSaveAdjust(summary.limit, amount, frequency, updatePeriod)
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ fun SpendingLimitScreen(
val uiState by viewModel.uiState.collectAsState()
val categoryUiState by categoryViewModel.uiState.collectAsState()
var showCreate by remember { mutableStateOf(false) }
var selectedLimitToAdjust by remember { mutableStateOf<SpendingLimitSummary?>(null) }

LaunchedEffect(Unit) {
categoryViewModel.getCategories(
Expand All @@ -39,6 +40,13 @@ fun SpendingLimitScreen(
}
}

LaunchedEffect(uiState.updateState) {
if (uiState.updateState is UiState.Success) {
selectedLimitToAdjust = null
viewModel.clearUpdateState()
}
}

if (showCreate) {
SpendingLimitCreateContent(
createState = uiState.createState,
Expand All @@ -59,6 +67,21 @@ fun SpendingLimitScreen(
limitsState = uiState.spendingLimitsState,
categories = categoryUiState.categories,
onAddClick = { showCreate = true },
updateState = uiState.updateState,
selectedLimitToAdjust = selectedLimitToAdjust,
onAdjustClick = { selectedLimitToAdjust = it },
onDismissAdjust = {
selectedLimitToAdjust = null
viewModel.clearUpdateState()
},
onSaveAdjust = { limit, amount, frequency, updatePeriod ->
viewModel.updateSpendingLimit(
limit = limit,
amount = amount,
frequency = frequency,
updatePeriod = updatePeriod
)
},
onBack = onNavigateBack
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import java.math.RoundingMode
data class SpendingLimitUiState(
val spendingLimitState: UiState<SpendingLimitSummary> = UiState.Idle,
val spendingLimitsState: UiState<List<SpendingLimitSummary>> = UiState.Idle,
val createState: UiState<SpendingLimit> = UiState.Idle
val createState: UiState<SpendingLimit> = UiState.Idle,
val updateState: UiState<SpendingLimit> = UiState.Idle
)

data class SpendingLimitSummary(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@ import com.resolum.intiva.core.common.state.UiState
import com.resolum.intiva.core.common.viewmodel.BaseViewModel
import com.resolum.intiva.core.network.model.NetworkResult
import com.resolum.intiva.features.finances.domain.models.CreateSpendingLimitRequest
import com.resolum.intiva.features.finances.domain.models.SpendingLimit
import com.resolum.intiva.features.finances.domain.models.SpendingLimitOwnerType
import com.resolum.intiva.features.finances.domain.models.SpendingLimitTargetType
import com.resolum.intiva.features.finances.domain.models.UpdateSpendingLimitAmountRequest
import com.resolum.intiva.features.finances.domain.models.UpdateSpendingLimitPeriodRequest
import com.resolum.intiva.features.finances.domain.usecase.CreateSpendingLimitUseCase
import com.resolum.intiva.features.finances.domain.usecase.GetSpendingLimitsUseCase
import com.resolum.intiva.features.finances.domain.usecase.UpdateSpendingLimitAmountUseCase
import com.resolum.intiva.features.finances.domain.usecase.UpdateSpendingLimitPeriodUseCase
import com.resolum.intiva.features.iam.domain.repositories.SessionRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
Expand All @@ -23,6 +28,8 @@ import javax.inject.Inject
class SpendingLimitViewModel @Inject constructor(
private val getSpendingLimitsUseCase: GetSpendingLimitsUseCase,
private val createSpendingLimitUseCase: CreateSpendingLimitUseCase,
private val updateSpendingLimitAmountUseCase: UpdateSpendingLimitAmountUseCase,
private val updateSpendingLimitPeriodUseCase: UpdateSpendingLimitPeriodUseCase,
private val sessionRepository: SessionRepository
) : BaseViewModel() {

Expand Down Expand Up @@ -185,6 +192,94 @@ class SpendingLimitViewModel @Inject constructor(
_uiState.update { it.copy(createState = UiState.Idle) }
}

fun updateSpendingLimit(
limit: SpendingLimit,
amount: String,
frequency: SpendingLimitFrequency,
updatePeriod: Boolean
) {
safeLaunch {
val limitAmount = amount.toBigDecimalOrNull()
if (limitAmount == null || limitAmount <= BigDecimal.ZERO) {
_uiState.update {
it.copy(updateState = UiState.Error("Ingresa un monto válido."))
}
return@safeLaunch
}

val period = frequency.toPeriod()
val amountChanged = limitAmount.compareTo(limit.limitAmount) != 0
val periodChanged = updatePeriod &&
(period.first.toString() != limit.startDate || period.second.toString() != limit.endDate)

if (!amountChanged && !periodChanged) {
_uiState.update { it.copy(updateState = UiState.Success(limit)) }
return@safeLaunch
}

_uiState.update { it.copy(updateState = UiState.Loading) }

if (amountChanged) {
val amountResult = updateSpendingLimitAmountUseCase(
spendingLimitId = limit.id,
request = UpdateSpendingLimitAmountRequest(
limitAmount = limitAmount,
currencyCode = limit.currencyCode
)
)

if (amountResult is NetworkResult.Error) {
_uiState.update {
it.copy(
updateState = UiState.Error(
message = amountResult.message,
throwable = amountResult.throwable
)
)
}
return@safeLaunch
}
}

if (periodChanged) {
when (
val periodResult = updateSpendingLimitPeriodUseCase(
spendingLimitId = limit.id,
request = UpdateSpendingLimitPeriodRequest(
startDate = period.first.toString(),
endDate = period.second.toString()
)
)
) {
is NetworkResult.Success -> {
_uiState.update { it.copy(updateState = UiState.Success(periodResult.data)) }
}

is NetworkResult.Error -> {
_uiState.update {
it.copy(
updateState = UiState.Error(
message = periodResult.message,
throwable = periodResult.throwable
)
)
}
return@safeLaunch
}
}
} else {
_uiState.update { it.copy(updateState = UiState.Success(limit.copy(limitAmount = limitAmount))) }
}

loadSpendingLimits()
loadMonthlySpendingLimit()
}
}

fun clearUpdateState() {
_uiState.update { it.copy(updateState = UiState.Idle) }
}

override fun handleError(throwable: Throwable) {
_uiState.update {
it.copy(
Expand Down
Loading