diff --git a/ui/src/main/java/br/com/tick/ui/core/TeiraNavigationDrawer.kt b/ui/src/main/java/br/com/tick/ui/core/TeiraNavigationDrawer.kt index 1f9d907..3549735 100644 --- a/ui/src/main/java/br/com/tick/ui/core/TeiraNavigationDrawer.kt +++ b/ui/src/main/java/br/com/tick/ui/core/TeiraNavigationDrawer.kt @@ -1,7 +1,13 @@ package br.com.tick.ui.core +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.material3.DismissibleDrawerSheet import androidx.compose.material3.DismissibleNavigationDrawer import androidx.compose.material3.DrawerState @@ -13,13 +19,16 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import androidx.navigation.NavBackStackEntry import br.com.tick.ui.NavigationItem import br.com.tick.ui.R import br.com.tick.ui.theme.spacing +import br.com.tick.ui.theme.textStyle import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -31,7 +40,6 @@ fun TeiraNavigationDrawer( navigateToRoute: (NavigationItem) -> Unit, content: @Composable () -> Unit ) { - val coroutineScope = rememberCoroutineScope() val currentRoute = navBackStackEntry?.destination?.route @@ -42,60 +50,73 @@ fun TeiraNavigationDrawer( drawerContainerColor = MaterialTheme.colorScheme.surface, drawerContentColor = MaterialTheme.colorScheme.onSurface ) { - Spacer(modifier = Modifier.height(MaterialTheme.spacing.large)) - TeiraNavigationDrawerItem( - drawerState = drawerState, - painter = painterResource(id = NavigationItem.Settings.iconResource), - text = stringResource(id = NavigationItem.Settings.titleResource), - coroutineScope = coroutineScope, - isCurrentRoute = currentRoute == NavigationItem.Settings.route - ) { - navigateToRoute(NavigationItem.Settings) - } - TeiraNavigationDrawerItem( - drawerState = drawerState, - painter = painterResource(id = NavigationItem.Wallet.iconResource), - text = stringResource(id = NavigationItem.Wallet.titleResource), - coroutineScope = coroutineScope, - isCurrentRoute = currentRoute == NavigationItem.Wallet.route + Column( + modifier = Modifier.fillMaxSize().padding(MaterialTheme.spacing.large), + verticalArrangement = Arrangement.SpaceBetween ) { - navigateToRoute(NavigationItem.Wallet) + Column { + Text( + modifier = Modifier.padding(MaterialTheme.spacing.large), + text = stringResource(id = R.string.app_name), + style = MaterialTheme.textStyle.h1extra, + color = MaterialTheme.colorScheme.tertiary + ) + Spacer(modifier = Modifier.height(MaterialTheme.spacing.large)) + TeiraNavigationDrawerItem( + drawerState = drawerState, + painter = painterResource(id = NavigationItem.Settings.iconResource), + text = stringResource(id = NavigationItem.Settings.titleResource), + isCurrentRoute = currentRoute == NavigationItem.Settings.route + ) { + navigateToRoute(NavigationItem.Settings) + } + TeiraNavigationDrawerItem( + drawerState = drawerState, + painter = painterResource(id = NavigationItem.Wallet.iconResource), + text = stringResource(id = NavigationItem.Wallet.titleResource), + isCurrentRoute = currentRoute == NavigationItem.Wallet.route + ) { + navigateToRoute(NavigationItem.Wallet) + } + TeiraNavigationDrawerItem( + drawerState = drawerState, + painter = painterResource(id = NavigationItem.Analysis.iconResource), + text = stringResource(id = NavigationItem.Analysis.titleResource), + isCurrentRoute = currentRoute == NavigationItem.Analysis.route + ) { + navigateToRoute(NavigationItem.Analysis) + } + Spacer( + modifier = Modifier + .padding(horizontal = MaterialTheme.spacing.medium, vertical = MaterialTheme.spacing.small) + .fillMaxWidth() + .height(0.5.dp) + .background(MaterialTheme.colorScheme.tertiary) + ) + TeiraNavigationDrawerItem( + drawerState = drawerState, + painter = painterResource(id = NavigationItem.History.iconResource), + text = stringResource(id = NavigationItem.History.titleResource), + isCurrentRoute = currentRoute == NavigationItem.History.route + ) { + navigateToRoute(NavigationItem.History) + } + TeiraNavigationDrawerItem( + drawerState = drawerState, + painter = painterResource(id = NavigationItem.Expense.iconResource), + text = stringResource(id = NavigationItem.Expense.titleResource), + isCurrentRoute = currentRoute == NavigationItem.Expense.route + ) { + navigateToParentRoute(NavigationItem.Expense) + } + } + TeiraNavigationDrawerItem( + drawerState = drawerState, + painter = painterResource(id = R.drawable.ic_clear), + text = stringResource(id = R.string.generic_close), + isCurrentRoute = false + ) } - TeiraNavigationDrawerItem( - drawerState = drawerState, - painter = painterResource(id = NavigationItem.Analysis.iconResource), - text = stringResource(id = NavigationItem.Analysis.titleResource), - coroutineScope = coroutineScope, - isCurrentRoute = currentRoute == NavigationItem.Analysis.route - ) { - navigateToRoute(NavigationItem.Analysis) - } - TeiraNavigationDrawerItem( - drawerState = drawerState, - painter = painterResource(id = NavigationItem.History.iconResource), - text = stringResource(id = NavigationItem.History.titleResource), - coroutineScope = coroutineScope, - isCurrentRoute = currentRoute == NavigationItem.History.route - ) { - navigateToRoute(NavigationItem.History) - } - TeiraNavigationDrawerItem( - drawerState = drawerState, - painter = painterResource(id = NavigationItem.Expense.iconResource), - text = stringResource(id = NavigationItem.Expense.titleResource), - coroutineScope = coroutineScope, - isCurrentRoute = currentRoute == NavigationItem.Expense.route - ) { - navigateToParentRoute(NavigationItem.Expense) - } - Spacer(modifier = Modifier.height(MaterialTheme.spacing.extraLarge)) - TeiraNavigationDrawerItem( - drawerState = drawerState, - painter = painterResource(id = R.drawable.ic_clear), - text = stringResource(id = R.string.generic_close), - coroutineScope = coroutineScope, - isCurrentRoute = false - ) } }, content = content @@ -107,10 +128,11 @@ private fun TeiraNavigationDrawerItem( drawerState: DrawerState, painter: Painter, text: String, - coroutineScope: CoroutineScope, isCurrentRoute: Boolean, onDrawerItemClick: (() -> Unit)? = null ) { + val coroutineScope = rememberCoroutineScope() + NavigationDrawerItem( icon = { Icon(painter = painter, contentDescription = null) diff --git a/ui/src/main/java/br/com/tick/ui/screens/analysis/AnalysisScreen.kt b/ui/src/main/java/br/com/tick/ui/screens/analysis/AnalysisScreen.kt index cc8b8c6..ccbb679 100644 --- a/ui/src/main/java/br/com/tick/ui/screens/analysis/AnalysisScreen.kt +++ b/ui/src/main/java/br/com/tick/ui/screens/analysis/AnalysisScreen.kt @@ -26,12 +26,12 @@ fun AnalysisScreen() { MostExpensiveCategory( modifier = Modifier .fillMaxWidth() - .padding(top = MaterialTheme.spacing.large) + .padding(top = MaterialTheme.spacing.small) ) FinancialHealthComposable( modifier = Modifier .fillMaxWidth() - .padding(top = MaterialTheme.spacing.large) + .padding(top = MaterialTheme.spacing.small) ) } } diff --git a/ui/src/main/java/br/com/tick/ui/screens/analysis/FinancialHealth.kt b/ui/src/main/java/br/com/tick/ui/screens/analysis/FinancialHealth.kt index 003f8ce..9dec2af 100644 --- a/ui/src/main/java/br/com/tick/ui/screens/analysis/FinancialHealth.kt +++ b/ui/src/main/java/br/com/tick/ui/screens/analysis/FinancialHealth.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel +import br.com.tick.sdk.domain.CurrencyFormat import br.com.tick.ui.R import br.com.tick.ui.core.TeiraNoAvailableDataState import br.com.tick.ui.screens.analysis.states.FinancialHealth diff --git a/ui/src/main/java/br/com/tick/ui/screens/analysis/MostExpensiveCategory.kt b/ui/src/main/java/br/com/tick/ui/screens/analysis/MostExpensiveCategory.kt index 456eac7..35223cd 100644 --- a/ui/src/main/java/br/com/tick/ui/screens/analysis/MostExpensiveCategory.kt +++ b/ui/src/main/java/br/com/tick/ui/screens/analysis/MostExpensiveCategory.kt @@ -1,10 +1,14 @@ package br.com.tick.ui.screens.analysis +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize 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.material3.Card @@ -23,8 +27,10 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import br.com.tick.sdk.domain.CurrencyFormat import br.com.tick.ui.R import br.com.tick.ui.core.TeiraNoAvailableDataState +import br.com.tick.ui.extensions.getLabelResource import br.com.tick.ui.screens.analysis.models.MostExpensiveCategory import br.com.tick.ui.screens.analysis.states.MostExpensiveCategoriesStates import br.com.tick.ui.screens.analysis.viewmodels.AnalysisScreenViewModel @@ -38,10 +44,11 @@ fun MostExpensiveCategory( ) { val mostExpensiveCategoriesStates by viewModel.mostExpenseCategoryList .collectAsState(initial = MostExpensiveCategoriesStates.NoDataAvailable) + val currency by viewModel.currency.collectAsState() Column(modifier = modifier.fillMaxWidth()) { when (val state = mostExpensiveCategoriesStates) { - is MostExpensiveCategoriesStates.Full -> MostExpensiveCategoryBody(modifier, state) + is MostExpensiveCategoriesStates.Full -> MostExpensiveCategoryBody(modifier, currency, state) MostExpensiveCategoriesStates.NoDataAvailable -> TeiraNoAvailableDataState(modifier) } } @@ -50,6 +57,7 @@ fun MostExpensiveCategory( @Composable private fun MostExpensiveCategoryBody( modifier: Modifier = Modifier, + currencyFormat: CurrencyFormat, mostExpensiveCategoriesState: MostExpensiveCategoriesStates.Full ) { Column(modifier = modifier.fillMaxWidth()) { @@ -59,7 +67,9 @@ private fun MostExpensiveCategoryBody( style = MaterialTheme.textStyle.h2 ) Row( - modifier = modifier.fillMaxWidth().padding(top = MaterialTheme.spacing.medium), + modifier = modifier + .fillMaxWidth() + .padding(top = MaterialTheme.spacing.medium), horizontalArrangement = Arrangement.SpaceEvenly ) { mostExpensiveCategoriesState.mostExpensiveCategories.forEach { mostExpensiveCategory -> @@ -67,9 +77,11 @@ private fun MostExpensiveCategoryBody( Color(it) } ?: MaterialTheme.colorScheme.secondary + val currencyLabel = stringResource(id = currencyFormat.getLabelResource()) + CategoryCard( label = mostExpensiveCategory.categoryName, - subLabel = mostExpensiveCategory.amount.toString(), + subLabel = "$currencyLabel${mostExpensiveCategory.amount}", color = categoryCardColor ) } @@ -83,7 +95,7 @@ fun CategoryCard(modifier: Modifier = Modifier, label: String, subLabel: String, modifier = modifier .size(80.dp) .padding(MaterialTheme.spacing.smallest), - colors = CardDefaults.cardColors(containerColor = color) + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface) ) { Column( modifier = Modifier.fillMaxSize(), @@ -93,15 +105,24 @@ fun CategoryCard(modifier: Modifier = Modifier, label: String, subLabel: String, Text( text = label, style = MaterialTheme.textStyle.h2bold, - color = MaterialTheme.colorScheme.onSecondary, + color = MaterialTheme.colorScheme.onTertiary, maxLines = 1, overflow = TextOverflow.Ellipsis ) Text( text = subLabel, - style = MaterialTheme.textStyle.h3small, - color = MaterialTheme.colorScheme.onSecondary + style = MaterialTheme.textStyle.h3, + color = MaterialTheme.colorScheme.onTertiary ) + Box(modifier = Modifier.fillMaxSize()) { + Spacer( + modifier = Modifier + .height(6.dp) + .background(color) + .align(Alignment.BottomCenter) + .fillMaxWidth() + ) + } } } } @@ -110,6 +131,7 @@ fun CategoryCard(modifier: Modifier = Modifier, label: String, subLabel: String, @Composable fun MostExpensiveCategoryBodyPreview() { MostExpensiveCategoryBody( + currencyFormat = CurrencyFormat.EURO, mostExpensiveCategoriesState = MostExpensiveCategoriesStates.Full( listOf(MostExpensiveCategory("Test", Color.Red.toArgb(), 56.0)) ) diff --git a/ui/src/main/java/br/com/tick/ui/screens/analysis/states/MostExpensiveCategoriesStates.kt b/ui/src/main/java/br/com/tick/ui/screens/analysis/states/MostExpensiveCategoriesStates.kt index 9b7f670..852185e 100644 --- a/ui/src/main/java/br/com/tick/ui/screens/analysis/states/MostExpensiveCategoriesStates.kt +++ b/ui/src/main/java/br/com/tick/ui/screens/analysis/states/MostExpensiveCategoriesStates.kt @@ -13,7 +13,7 @@ sealed class MostExpensiveCategoriesStates { Full( mostExpensiveCategories .sortedByDescending { it.amount } - .take(5) + .take(4) ) } } diff --git a/ui/src/main/java/br/com/tick/ui/screens/analysis/viewmodels/AnalysisScreenViewModel.kt b/ui/src/main/java/br/com/tick/ui/screens/analysis/viewmodels/AnalysisScreenViewModel.kt index f6d7c2f..8037d97 100644 --- a/ui/src/main/java/br/com/tick/ui/screens/analysis/viewmodels/AnalysisScreenViewModel.kt +++ b/ui/src/main/java/br/com/tick/ui/screens/analysis/viewmodels/AnalysisScreenViewModel.kt @@ -1,7 +1,10 @@ package br.com.tick.ui.screens.analysis.viewmodels import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import br.com.tick.sdk.dispatchers.DispatcherProvider +import br.com.tick.sdk.domain.CurrencyFormat +import br.com.tick.sdk.repositories.user.UserRepository import br.com.tick.ui.screens.analysis.states.AnalysisGraphStates import br.com.tick.ui.screens.analysis.states.FinancialHealth import br.com.tick.ui.screens.analysis.states.MostExpensiveCategoriesStates @@ -10,8 +13,15 @@ import br.com.tick.ui.screens.analysis.usecases.FetchLastMonthExpenses import br.com.tick.ui.screens.analysis.usecases.GetMostExpensiveCategories import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel @@ -19,6 +29,7 @@ class AnalysisScreenViewModel @Inject constructor( private val fetchLastMonthExpenses: FetchLastMonthExpenses, private val getMostExpensiveCategories: GetMostExpensiveCategories, private val calculateFinancialHealthSituation: CalculateFinancialHealthSituation, + userRepository: UserRepository, private val dispatcherProvider: DispatcherProvider ) : ViewModel() { @@ -36,10 +47,23 @@ class AnalysisScreenViewModel @Inject constructor( } }.flowOn(dispatcherProvider.io()) - val financialHealthSituation: Flow - get() = flow { + val currency = userRepository.getUser() + .flowOn(dispatcherProvider.io()) + .map { it.currency } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = CurrencyFormat.EURO + ) + + private val _financialHealthSituation = MutableStateFlow(FinancialHealth.NoDataAvailable) + val financialHealthSituation: Flow = _financialHealthSituation + + init { + viewModelScope.launch(dispatcherProvider.io()) { calculateFinancialHealthSituation().collect { - emit(it) + _financialHealthSituation.emit(it) } - }.flowOn(dispatcherProvider.io()) -} \ No newline at end of file + } + } +} diff --git a/ui/src/main/java/br/com/tick/ui/theme/Spacing.kt b/ui/src/main/java/br/com/tick/ui/theme/Spacing.kt index c827474..d8fdc03 100644 --- a/ui/src/main/java/br/com/tick/ui/theme/Spacing.kt +++ b/ui/src/main/java/br/com/tick/ui/theme/Spacing.kt @@ -37,6 +37,13 @@ data class TeiraTextStyle( lineHeight = 24.sp, letterSpacing = 3.sp ), + val h1extra: TextStyle = TextStyle( + fontFamily = Mulish, + fontWeight = FontWeight.Bold, + fontSize = 26.sp, + lineHeight = 24.sp, + letterSpacing = 3.sp + ), val h2: TextStyle = TextStyle( fontFamily = Mulish, fontWeight = FontWeight.Medium, diff --git a/ui/src/main/res/values/strings_analysis.xml b/ui/src/main/res/values/strings_analysis.xml index 6cfd3c3..428e752 100644 --- a/ui/src/main/res/values/strings_analysis.xml +++ b/ui/src/main/res/values/strings_analysis.xml @@ -5,7 +5,7 @@ Most expensive categories Financial Health - Your expenses is %1\$.2f percent of your income. %2\$s! + Your expenses is %1$.2f%% of your income. %2$s! You are safe! You need to be cautious.