Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c41c15a
feat(profiles): add profile dto models and remote mapper.
equinox-1092 Jun 10, 2026
61ca547
feat(profiles): add profilerepositoryimpl and profilerepository inter…
equinox-1092 Jun 10, 2026
6b887a3
feat(profiles): add profile use cases.
equinox-1092 Jun 10, 2026
c1532b9
feat(profiles): add profilemodule with hilt bindings.
equinox-1092 Jun 10, 2026
25e5e27
feat(profiles): add profileviewmodel and profileuistate.
equinox-1092 Jun 10, 2026
50acf44
Merge pull request #48 from Resolum/feature/user-profile-management
Giovany7x Jun 10, 2026
02548b5
feat(profile): improve settings screen UI
Giovany7x Jun 12, 2026
e8f1d65
feat(navigation): connect settings to categories
Giovany7x Jun 12, 2026
bd102f3
feat(profile): improve profile screen UI
Giovany7x Jun 12, 2026
e4760cc
fix(category): update service endpoints
Giovany7x Jun 12, 2026
b5e8c7b
fix(category): align create request dto
Giovany7x Jun 12, 2026
da5bce1
fix(category): update facade creation flow
Giovany7x Jun 12, 2026
7c8c711
fix(category): send owner and type on creation
Giovany7x Jun 12, 2026
e28a924
fix(category): load expense categories by default
Giovany7x Jun 12, 2026
825da4e
Merge pull request #49 from Resolum/feature/payment-methods-and-categ…
Far14z Jun 12, 2026
b74fad6
Merge pull request #50 from Resolum/feature/user-profile-management
Far14z Jun 12, 2026
0f6a8dc
feat(transactions): implement transaction detail screen and add trans…
Far14z Jun 12, 2026
1ba6596
refactor(deployment): simplify app version setting logic in distribut…
Far14z Jun 12, 2026
5345d5f
Merge pull request #51 from Resolum/feature/transactions
equinox-1092 Jun 12, 2026
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
18 changes: 2 additions & 16 deletions .github/workflows/firebase-app-distribution.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@ name: Intiva Firebase App Distribution
on:
push:
branches:
- main
- 'release/**'
release:
types:
- published
workflow_dispatch:

jobs:
Expand Down Expand Up @@ -84,17 +80,7 @@ jobs:

- name: Set app version
run: |
RAW_VERSION=""

if [[ "${GITHUB_EVENT_NAME}" == "release" ]]; then
RAW_VERSION="${{ github.event.release.tag_name }}"
elif [[ "${GITHUB_REF_NAME}" == release/* ]]; then
RAW_VERSION="${GITHUB_REF_NAME#release/}"
elif [[ "${GITHUB_REF_NAME}" == "main" ]]; then
RAW_VERSION="$(git describe --tags --abbrev=0 2>/dev/null || true)"
else
RAW_VERSION="1.0.0"
fi
RAW_VERSION="${GITHUB_REF_NAME#release/}"

if [[ -z "$RAW_VERSION" ]]; then
RAW_VERSION="1.0.0"
Expand Down Expand Up @@ -122,4 +108,4 @@ jobs:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: ./gradlew appDistributionUploadRelease
run: ./gradlew appDistributionUploadRelease
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,14 @@ fun AppNavGraph(
}

composable(Screen.MainShell.route) {
MainShell()
MainShell(
onLogout = {
navController.navigateAndClearBackStack(
route = Screen.SignIn.route,
popUpTo = Screen.MainShell.route,
)
}
)
}
}
}
120 changes: 106 additions & 14 deletions app/src/main/java/com/resolum/intiva/core/navigation/graph/MainShell.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.resolum.intiva.features.finances.domain.models.TransactionType
import com.resolum.intiva.features.finances.presentation.HomeScreen
import com.resolum.intiva.features.finances.presentation.spendinglimits.SpendingLimitScreen
import com.resolum.intiva.features.finances.presentation.transactions.TransactionFormScreen
import com.resolum.intiva.features.finances.presentation.transactions.TransactionDetailScreen
import com.resolum.intiva.features.finances.presentation.transactions.TransactionsScreen
import com.resolum.intiva.features.paymentmethodsandcategories.presentation.category.ManageCategoriesScreen
import com.resolum.intiva.features.paymentmethodsandcategories.presentation.financialaccount.CreateFinancialAccountScreen
Expand All @@ -29,30 +30,43 @@ import com.resolum.intiva.features.savings.presentation.completion.GoalCompleted
import com.resolum.intiva.features.savings.presentation.completion.GoalUncompletedScreen
import com.resolum.intiva.features.savings.presentation.contribute.ContributeToGoalScreen
import com.resolum.intiva.features.shared.domain.model.OwnerType
import com.resolum.intiva.features.profiles.presentation.ProfileScreen
import com.resolum.intiva.features.profiles.presentation.EditProfileScreen
import com.resolum.intiva.features.profiles.presentation.ConfiguracionScreen
import com.resolum.intiva.features.profiles.presentation.PrivacidadSeguridadScreen
import com.resolum.intiva.features.profiles.presentation.CentroAyudaScreen
import com.resolum.intiva.features.profiles.presentation.NotificacionesScreen
import com.resolum.intiva.features.profiles.presentation.AparienciaScreen


/**
* Main shell of the app, containing the bottom navigation and root-level destinations.
* Each feature's main screen should be registered here, with deeper navigation handled within the feature's own nav graph.
*/
@Composable
fun MainShell() {
fun MainShell(
onLogout: () -> Unit = {}
) {
val shellNavController = rememberNavController()
val navBackStackEntry by shellNavController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route

Scaffold(
containerColor = Color.White,
bottomBar = {
IntivaBottomNavBar(
currentRoute = currentRoute,
onNavigate = { route ->
shellNavController.navigate(route) {
popUpTo(NavRoutes.HOME) { inclusive = false }
launchSingleTop = true
}
},
)
}, contentWindowInsets = WindowInsets(0)
if (currentRoute in NavRoutes.BOTTOM_NAV_ROUTES) {
IntivaBottomNavBar(
currentRoute = currentRoute,
onNavigate = { route ->
shellNavController.navigate(route) {
popUpTo(NavRoutes.HOME) { inclusive = false }
launchSingleTop = true
}
},
)
}
},
contentWindowInsets = WindowInsets(0)
) { padding ->
NavHost(
navController = shellNavController,
Expand All @@ -69,19 +83,96 @@ fun MainShell() {
onNavigateToSpendingLimitAlert = {
shellNavController.navigate(NavRoutes.SPENDING_LIMIT_ALERT)
},
onNavigateToTransactionDetail = { transactionId ->
shellNavController.navigate(NavRoutes.transactionDetail(transactionId))
},
)
}

composable(NavRoutes.TRANSACTIONS) {
TransactionsScreen()
TransactionsScreen(
onNavigateToTransactionDetail = { transactionId ->
shellNavController.navigate(NavRoutes.transactionDetail(transactionId))
}
)
}

composable(NavRoutes.TRANSACTION_DETAIL) { backStackEntry ->
val transactionId = backStackEntry.arguments
?.getString("transactionId")
?.toLongOrNull()
?: return@composable
TransactionDetailScreen(
transactionId = transactionId,
onNavigateBack = { shellNavController.popBackStack() }
)
}

composable(NavRoutes.SPENDING_LIMIT_ALERT) {
SpendingLimitScreen(onNavigateBack = { shellNavController.popBackStack() })
}

composable(NavRoutes.FAMILY) { }
composable(NavRoutes.PROFILE) { }

composable(NavRoutes.PROFILE) {
ProfileScreen(
onNavigateToEdit = { shellNavController.navigate(NavRoutes.EDIT_PROFILE) },
onNavigateToConfig = { shellNavController.navigate(NavRoutes.CONFIG) },
onPrivacyClick = { shellNavController.navigate(NavRoutes.PRIVACY) },
onHelpClick = { shellNavController.navigate(NavRoutes.HELP) },
onLogoutClick = onLogout
)
}

composable(NavRoutes.EDIT_PROFILE) {
EditProfileScreen(
onNavigateBack = { shellNavController.popBackStack() }
)
}

composable(NavRoutes.CONFIG) {
ConfiguracionScreen(
onNavigateToPersonalDetails = {
shellNavController.navigate(NavRoutes.EDIT_PROFILE)
},
onNavigateToCategories = {
shellNavController.navigate(NavRoutes.MANAGE_CATEGORIES)
},
onNavigateToNotifications = {
shellNavController.navigate(NavRoutes.NOTIFICATIONS)
},
onNavigateToAppearance = {
shellNavController.navigate(NavRoutes.APPEARANCE)
},
onNavigateBack = {
shellNavController.popBackStack()
}
)
}

composable(NavRoutes.PRIVACY) {
PrivacidadSeguridadScreen(
onNavigateBack = { shellNavController.popBackStack() }
)
}

composable(NavRoutes.HELP) {
CentroAyudaScreen(
onNavigateBack = { shellNavController.popBackStack() }
)
}

composable(NavRoutes.NOTIFICATIONS) {
NotificacionesScreen(
onNavigateBack = { shellNavController.popBackStack() }
)
}

composable(NavRoutes.APPEARANCE) {
AparienciaScreen(
onNavigateBack = { shellNavController.popBackStack() }
)
}

// Payment methods and categories
composable(NavRoutes.MANAGE_CATEGORIES) {
Expand Down Expand Up @@ -252,6 +343,7 @@ fun MainShell() {
ownerType = OwnerType.INDIVIDUAL
)
}

}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package com.resolum.intiva.core.navigation.routes
object NavRoutes {
const val HOME = "home"
const val TRANSACTIONS = "transactions"
const val TRANSACTION_DETAIL = "transaction_detail/{transactionId}"
const val SPENDING_LIMIT_ALERT = "spending_limit_alert"
const val SAVINGS_GOALS = "savings_goals"
const val SAVINGS_GOAL_CREATE = "savings_goal_create/{accountId}"
Expand All @@ -16,10 +17,18 @@ object NavRoutes {
const val SAVINGS_GOAL_UNCOMPLETED = "savings_goal_uncompleted/{accountId}/{goalId}"
const val FAMILY = "family"
const val PROFILE = "profile"
const val EDIT_PROFILE = "edit_profile"
const val CONFIG = "config"
const val PRIVACY = "privacy"
const val HELP = "help"
const val NOTIFICATIONS = "notifications"
const val APPEARANCE = "appearance"

const val NEW_INCOME = "transactions/new_income"
const val NEW_EXPENSE = "transaction/new_expense"

fun transactionDetail(transactionId: Long) = "transaction_detail/$transactionId"

const val MANAGE_CATEGORIES = "manage_categories"

const val FINANCIAL_ACCOUNTS = "financial_accounts"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class TransactionFacadeService @Inject constructor(
*/
suspend fun getTransactionsByOwnerId(ownerId: Long, transactionType: String?) = transactionService.getTransactionsByOwnerId(ownerId, transactionType)

suspend fun getTransactionById(id: Long) = transactionService.getTransactionById(id)

/**
* Retrieves the latest transactions for a specific owner.
*
Expand All @@ -41,4 +43,4 @@ class TransactionFacadeService @Inject constructor(
*/
suspend fun getLastestTransactionByOwnerId(ownerId: Long) = transactionService.getLastestTransactionByOwnerId(ownerId)

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.resolum.intiva.features.finances.data.remote.mappers
import com.resolum.intiva.features.finances.data.remote.models.TransactionResponseDto
import com.resolum.intiva.features.finances.data.remote.models.TransactionWithDesignResponseDto
import com.resolum.intiva.features.finances.domain.models.Transaction
import com.resolum.intiva.features.paymentmethodsandcategories.data.remote.mappers.toDomain

/**
* Extension function to map a [Transaction] domain model to a [TransactionWithDesignResponseDto].
Expand All @@ -19,8 +20,11 @@ fun TransactionResponseDto.toDomain(): Transaction {
description = description,
ownerId = ownerId,
financialAccountId = financialAccountId,
financialAccountName = financialAccountName,
actorUserId = actorUserId,
transactionType = transactionType,
categoryId = categoryId,
registeredAt = registeredAt,
financialAccount = financialAccount?.toDomain(),
)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.resolum.intiva.features.finances.data.remote.models

import com.google.gson.annotations.SerializedName
import com.resolum.intiva.features.paymentmethodsandcategories.data.remote.models.FinancialAccountResponseDto

/**
* Data Transfer Object (DTO) representing a financial transaction response from the API.
Expand Down Expand Up @@ -35,6 +36,9 @@ data class TransactionResponseDto(
@SerializedName("financialAccountId")
val financialAccountId: Long,

@SerializedName("financialAccountName")
val financialAccountName: String? = null,

@SerializedName("actorUserId")
val actorUserId: Long,

Expand All @@ -43,4 +47,10 @@ data class TransactionResponseDto(

@SerializedName("categoryId")
val categoryId: Long?,
)

@SerializedName("registeredAt")
val registeredAt: String? = null,

@SerializedName(value = "financialAccount", alternate = ["account"])
val financialAccount: FinancialAccountResponseDto? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ interface TransactionService {
@Query("transactionType") transactionType: String? = null,
) : ResponseWrapperDto<List<TransactionGroupByDateResponseDto>>

@GET("transactions/{id}")
suspend fun getTransactionById(
@Path("id") id: Long,
) : TransactionResponseDto

/**
* Makes a GET request to the "transactions/lastest" endpoint to retrieve the latest transactions for a specific owner.
*
Expand All @@ -54,4 +59,4 @@ interface TransactionService {
@Query("ownerId") ownerId: Long,
) : ResponseWrapperDto<List<TransactionGroupByDateResponseDto>>

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ class TransactionRepositoryImpl @Inject constructor(
return result
}

override suspend fun getTransactionById(id: Long): NetworkResult<Transaction> {
val result = safeCall {
transactionFacadeService.getTransactionById(id).toDomain()
}

return result
}

/**
* Retrieves the latest transactions for a specific owner by making an API call through the [TransactionFacadeService].
*
Expand All @@ -93,4 +101,4 @@ class TransactionRepositoryImpl @Inject constructor(
return result
}

}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.resolum.intiva.features.finances.domain.models

import com.resolum.intiva.features.paymentmethodsandcategories.domain.models.FinancialAccount

/**
* Represents a financial transaction resource with all its details.
*
Expand All @@ -20,7 +22,10 @@ data class Transaction(
val description: String,
val ownerId: Long,
val financialAccountId: Long,
val financialAccountName: String? = null,
val actorUserId: Long,
val transactionType: String,
val categoryId: Long?
val categoryId: Long?,
val registeredAt: String? = null,
val financialAccount: FinancialAccount? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ interface TransactionRepository {
*/
suspend fun getTransactionsByOwnerId(transactionType: String?) : NetworkResult<List<TransactionGroupByDate>>

suspend fun getTransactionById(id: Long): NetworkResult<Transaction>

/**
* Retrieves the latest transactions for a specific owner.
*
* @return A [NetworkResult] containing a list of [TransactionGroupByDate] representing the latest transactions if successful, or an error if the operation fails.
*/
suspend fun getLastestTransactionsByOwnerId() : NetworkResult<List<TransactionGroupByDate>>
}
}
Loading
Loading