Skip to content

Commit

Permalink
Adding home screen logic
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanarodr committed May 2, 2024
1 parent 5c8b38a commit a6ca847
Show file tree
Hide file tree
Showing 14 changed files with 364 additions and 52 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package br.com.stonks.navigation

enum class MainNavDestination(val route: String) {
HOME("home"),
STOCK("stock"),
}
4 changes: 2 additions & 2 deletions app/src/main/java/br/com/stonks/ui/view/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import androidx.compose.ui.graphics.Color
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import br.com.stonks.feature.home.ui.view.HomeRemoteScreen
import br.com.stonks.feature.home.ui.view.HomeScreen
import br.com.stonks.feature.stocks.ui.view.StockAlertScreen
import br.com.stonks.navigation.MainNavDestination

Expand Down Expand Up @@ -53,7 +53,7 @@ class MainActivity : ComponentActivity() {
modifier = Modifier.padding(innerPadding)
) {
composable(route = MainNavDestination.HOME.route) {
HomeRemoteScreen(
HomeScreen(
snackbarHostState = snackbarHostState,
)
}
Expand Down
4 changes: 3 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<resources>
<string name="app_name">Stonks</string>
</resources>
<string name="main_nav_action_home">Home</string>
<string name="main_nav_action_stock_alert">Stock Alert</string>
</resources>
9 changes: 9 additions & 0 deletions design-system/src/main/res/drawable/ic_more.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#FF000000"
android:pathData="M240,560q-33,0 -56.5,-23.5T160,480q0,-33 23.5,-56.5T240,400q33,0 56.5,23.5T320,480q0,33 -23.5,56.5T240,560ZM480,560q-33,0 -56.5,-23.5T400,480q0,-33 23.5,-56.5T480,400q33,0 56.5,23.5T560,480q0,33 -23.5,56.5T480,560ZM720,560q-33,0 -56.5,-23.5T640,480q0,-33 23.5,-56.5T720,400q33,0 56.5,23.5T800,480q0,33 -23.5,56.5T720,560Z"/>
</vector>
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package br.com.stonks.feature.home.di

import br.com.stonks.common.states.ViewModelState
import br.com.stonks.feature.home.domain.mapper.DailyTransactionMapper
import br.com.stonks.feature.home.domain.mapper.WalletMapper
import br.com.stonks.feature.home.domain.usecase.DailyTransactionUseCase
Expand All @@ -9,9 +10,13 @@ import br.com.stonks.feature.home.repository.HomeRepository
import br.com.stonks.feature.home.repository.HomeRepositoryImpl
import br.com.stonks.feature.home.repository.remote.HomeApiService
import br.com.stonks.feature.home.repository.remote.HomeRemoteDataSource
import br.com.stonks.feature.home.ui.mapper.HomeUiMapper
import br.com.stonks.feature.home.ui.viewmodel.HOME_VM_QUALIFIER
import br.com.stonks.feature.home.ui.viewmodel.HomeViewModel
import br.com.stonks.infrastructure.network.provider.NetworkServiceProvider
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.core.qualifier.named
import org.koin.dsl.bind
import org.koin.dsl.module

val homeModule = module {
Expand Down Expand Up @@ -67,10 +72,10 @@ val homeModule = module {
)
}

viewModel {
viewModel(qualifier = named(HOME_VM_QUALIFIER)) {
HomeViewModel(
homeContentUseCase = get(),
homeUiMapper = get(),
)
}
} bind ViewModelState::class
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package br.com.stonks.feature.home.domain.usecase

import br.com.stonks.feature.home.domain.model.HomeContentModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine

internal class HomeContentUseCase(
private val walletUseCase: WalletUseCase,
private val dailyTransactionUseCase: DailyTransactionUseCase,
) {

suspend fun fetchData(): Flow<HomeContentModel> {
return walletUseCase().combine(dailyTransactionUseCase()) { wallet, transactions ->
HomeContentModel(wallet, transactions)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package br.com.stonks.feature.home.ui.mapper

import br.com.stonks.common.formatters.DATE_PATTERN_DD_MMMM_BR
import br.com.stonks.common.formatters.formatTo
import br.com.stonks.common.mapper.Mapper
import br.com.stonks.designsystem.components.PieChartData
import br.com.stonks.designsystem.components.PieChartDataProgress
import br.com.stonks.feature.home.domain.model.DailyTransactionModel
import br.com.stonks.feature.home.domain.model.HomeContentModel
import br.com.stonks.feature.home.domain.model.PortfolioModel
import br.com.stonks.feature.home.domain.model.TransactionModel
import br.com.stonks.feature.home.domain.model.WalletModel
import br.com.stonks.feature.home.ui.model.DailyTransactionUiModel
import br.com.stonks.feature.home.ui.model.HomeUiModel
import br.com.stonks.feature.home.ui.model.PortfolioUiModel
import br.com.stonks.feature.home.ui.model.TransactionUiModel

internal class HomeUiMapper : Mapper<HomeContentModel, HomeUiModel> {

override fun mapper(input: HomeContentModel) = HomeUiModel(
totalAssets = input.wallet.totalAssets,
portfolioChart = input.wallet.toPieChart(),
portfolio = input.wallet.portfolio.map(::mapperPortfolio),
dailyTransactions = input.dailyTransactions.map(::mapperDailyGroup),
)

private fun WalletModel.toPieChart(): PieChartData {
var incrementalProgress = 0f

val dataProgress = this.portfolio.map {
incrementalProgress += it.allocation

PieChartDataProgress(
progress = incrementalProgress,
progressColor = it.portfolioType.getColor()
)
}.sortedByDescending { it.progress }

return PieChartData(
title = "Todos os produtos",
value = this.investedBalance,
dataProgress = dataProgress,
)
}

private fun mapperPortfolio(input: PortfolioModel) = PortfolioUiModel(
tagColor = input.portfolioType.getColor(),
portfolioName = input.portfolioName,
totalInvestment = input.totalInvestment,
allocation = input.allocation,
)

private fun mapperDailyGroup(input: DailyTransactionModel) = DailyTransactionUiModel(
dateGroup = input.date.formatTo(DATE_PATTERN_DD_MMMM_BR),
dailyBalance = input.dailyBalance,
transactions = input.transactions.map(::mapperTransaction),
)

private fun mapperTransaction(input: TransactionModel) = TransactionUiModel(
icon = input.type.getIcon(),
description = input.description,
value = input.value,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package br.com.stonks.feature.home.ui.mapper

import androidx.compose.ui.graphics.Color
import br.com.stonks.designsystem.tokens.ColorToken
import br.com.stonks.feature.home.domain.types.PortfolioType
import br.com.stonks.feature.home.domain.types.TransactionType

fun PortfolioType.getColor(): Color {
return when (this) {
PortfolioType.DIGITAL_ACCOUNT -> ColorToken.HighlightOrange
PortfolioType.INVESTMENT_FUNDS -> ColorToken.HighlightBlue
PortfolioType.GOVERNMENT_BONDS -> ColorToken.HighlightPurple
PortfolioType.STOCK -> ColorToken.HighlightGreen
else -> ColorToken.Grayscale200
}
}

fun TransactionType.getIcon(): Int {
return when (this) {
TransactionType.INCOME -> br.com.stonks.designsystem.R.drawable.ic_income
TransactionType.EXPENSIVE -> br.com.stonks.designsystem.R.drawable.ic_expensive
TransactionType.UNKNOWN -> br.com.stonks.designsystem.R.drawable.ic_more
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package br.com.stonks.feature.home.ui.model

import androidx.annotation.DrawableRes
import androidx.compose.ui.graphics.Color
import br.com.stonks.designsystem.components.PieChartData

internal data class PortfolioUiModel(
val tagColor: Color,
val portfolioName: String,
val totalInvestment: Double,
val allocation: Float,
)

internal data class TransactionUiModel(
@DrawableRes val icon: Int,
val description: String,
val value: Double,
)

internal data class DailyTransactionUiModel(
val dateGroup: String,
val dailyBalance: Double,
val transactions: List<TransactionUiModel>,
)

internal data class HomeUiModel(
val totalAssets: Double,
val portfolioChart: PieChartData,
val portfolio: List<PortfolioUiModel>,
val dailyTransactions: List<DailyTransactionUiModel>,
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ internal sealed class HomeUiState : UiState {

data object Loading : HomeUiState()

data class WalletSuccess(val data: WalletModel) : HomeUiState()
data class Success(val data: HomeUiModel) : HomeUiState()

data class DailyTransactionSuccess(val data: DailyTransactionModel) : HomeUiState()

data class Error(val exception: StonksApiException) : HomeUiState()
data class Error(val exception: Throwable) : HomeUiState()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package br.com.stonks.feature.home.ui.view

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import br.com.stonks.common.formatters.formatCurrency
import br.com.stonks.common.states.ViewModelState
import br.com.stonks.designsystem.components.HeaderLayout
import br.com.stonks.designsystem.components.PieChartData
import br.com.stonks.designsystem.components.PieChartDataProgress
import br.com.stonks.designsystem.components.PieChartLayout
import br.com.stonks.designsystem.components.SnackbarLayout
import br.com.stonks.designsystem.tokens.ColorToken
import br.com.stonks.designsystem.tokens.SpacingToken
import br.com.stonks.feature.home.R
import br.com.stonks.feature.home.ui.model.DailyTransactionUiModel
import br.com.stonks.feature.home.ui.model.HomeUiModel
import br.com.stonks.feature.home.ui.model.PortfolioUiModel
import br.com.stonks.feature.home.ui.model.TransactionUiModel
import br.com.stonks.feature.home.ui.states.HomeUiState
import br.com.stonks.feature.home.ui.viewmodel.HOME_VM_QUALIFIER
import org.koin.androidx.compose.koinViewModel
import org.koin.core.qualifier.named
import timber.log.Timber

@Composable
private fun SessionDivider(
thickness: Dp = SpacingToken.sm,
) {
Spacer(modifier = Modifier.height(thickness))
}

@Composable
private fun HomeContent(
uiModel: HomeUiModel,
modifier: Modifier = Modifier,
) {
LazyColumn(
modifier = modifier,
contentPadding = PaddingValues(SpacingToken.xl),
verticalArrangement = Arrangement.spacedBy(
space = SpacingToken.xl,
),
) {
item {
HeaderLayout(
title = stringResource(id = R.string.total_balance),
subtitle = uiModel.totalAssets.formatCurrency(),
)
}
item {
SessionDivider()
Text(
text = stringResource(id = R.string.wallet_title),
style = MaterialTheme.typography.titleMedium,
)
}
item {
SessionDivider()
PieChartLayout(
data = uiModel.portfolioChart,
)
}
items(uiModel.portfolio) {
PortfolioCard(
uiModel = it,
)
}
items(uiModel.dailyTransactions) { group ->
SessionDivider()
TransactionGroupLayout(
title = group.dateGroup,
subtitle = stringResource(
id = R.string.daily_balance, group.dailyBalance.formatCurrency(),
),
)
group.transactions.forEach { transaction ->
TransactionItemLayout(
icon = transaction.icon,
description = transaction.description,
value = transaction.value.formatCurrency(),
)
}
}
}
}

@Composable
fun HomeScreen(
modifier: Modifier = Modifier,
viewModel: ViewModelState<*, *> = koinViewModel(qualifier = named(HOME_VM_QUALIFIER)),
snackbarHostState: SnackbarHostState,
) {
val uiState = viewModel.uiState.collectAsStateWithLifecycle()

when (uiState.value) {
is HomeUiState.Loading -> {
Timber.i("Loading home screen...")
}

is HomeUiState.Success -> {
HomeContent(
uiModel = (uiState.value as HomeUiState.Success).data,
modifier = modifier,
)
}

is HomeUiState.Error -> {
SnackbarLayout(
snackbarHostState = snackbarHostState,
message = "Ops, algo deu errado tente novamente",
)
}
}
}

@Preview(showBackground = true)
@Composable
private fun HomeScreenPreview() {
HomeContent(
uiModel = HomeUiModel(
totalAssets = 166300.0,
portfolioChart = PieChartData(
title = "Todos os produtos",
value = 160000.0,
dataProgress = listOf(
PieChartDataProgress(
progress = 1f,
progressColor = ColorToken.HighlightGreen,
),
),
),
portfolio = listOf(
PortfolioUiModel(
tagColor = ColorToken.HighlightGreen,
portfolioName = "Ações",
totalInvestment = 1000.0,
allocation = 0.3043f,
)
),
dailyTransactions = listOf(
DailyTransactionUiModel(
dateGroup = "05 de Maio",
dailyBalance = 100.0,
transactions = listOf(
TransactionUiModel(
icon = br.com.stonks.designsystem.R.drawable.ic_income,
description = "Resgate",
value = 100.0,
)
),
)
)
)
)
}
Loading

0 comments on commit a6ca847

Please sign in to comment.