Skip to content

Commit

Permalink
feature/screen categories (#23)
Browse files Browse the repository at this point in the history
* Creating CategoryScreen

* Filter Category

* CategoryCardTest

* Creating EventManeger and emit event the Category to NewOperationScreen

* Creating CategoryScreen

* Filter Category

* CategoryCardTest

* Creating EventManeger and emit event the Category to NewOperationScreen

* Resolve conflicts

The EventManager class was deleted due to it is functionally being really similar to the UiState sealed interface.

---------

Co-authored-by: Lise <[email protected]>
  • Loading branch information
guiBrisson and luararamos authored May 24, 2024
1 parent e2895e7 commit 12b2f1b
Show file tree
Hide file tree
Showing 14 changed files with 383 additions and 114 deletions.
11 changes: 9 additions & 2 deletions composeApp/src/commonMain/kotlin/domain/model/Category.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ import presentation.theme.rose500
import presentation.theme.rose600

@OptIn(ExperimentalResourceApi::class)
enum class Category(val categoryName: String, val primaryColor: Color,
val secondaryColor: Color, val icon: DrawableResource) {
enum class Category(
val categoryName: String, val primaryColor: Color,
val secondaryColor: Color, val icon: DrawableResource,
) {
HEALTH_WELLNESS("Health & Wellness", red600, red500, Res.drawable.ic_health),
ENTERTAINMENT("Entertainment", orange600, orange500, Res.drawable.ic_entertainment),
SUBSCRIPTION("Subscription", amber500, amber600, Res.drawable.ic_subscription),
Expand All @@ -55,6 +57,11 @@ enum class Category(val categoryName: String, val primaryColor: Color,

companion object {
fun all(): Array<Category> = enumValues<Category>()

fun filterCategory(query: String): Array<Category> {
return enumValues<Category>().filter { it.categoryName.contains(query, ignoreCase = true) }.toTypedArray()
}

fun default() = ENTERTAINMENT
}
}
20 changes: 18 additions & 2 deletions composeApp/src/commonMain/kotlin/domain/model/Operation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,28 @@ package domain.model

import kotlinx.datetime.LocalDateTime

data class Operation (
data class Operation(
val id: Long? = null,
val amount: Double = 0.0,
val description: String = "",
val type: OperationType = OperationType.default(),
val category: Category = Category.default(),
val date: LocalDateTime? = null,
val isPeriodic: Boolean = false
)
) {
constructor(
amount: String,
description: String,
type: OperationType,
category: Category,
isPeriodic: Boolean
) : this(
id = null,
amount = amount.toDoubleOrNull() ?: 0.0,
description = description,
type = type,
category = category,
date = null,
isPeriodic = isPeriodic,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,19 @@ fun FloatingButton(
}

@Composable
fun LoadingButton(modifier: Modifier, text: String, enabled: Boolean = true,
state: UIState<Any>, onButtonClick: () -> Unit, onSnackClick: (Boolean) -> Unit
fun LoadingButton(
modifier: Modifier,
text: String,
enabled: Boolean = true,
state: UIState<Any>,
onButtonClick: () -> Unit,
onSnackClick: (Boolean) -> Unit,
) {
val transition = updateTransition(
targetState = state,
label = "main transition",
)

val horizontalContentPadding by transition.animateDp(
transitionSpec = {
spring(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package presentation.designsystem

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import domain.model.Category
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.painterResource
import presentation.theme.*

@OptIn(ExperimentalResourceApi::class)
@Composable
fun CategoryCard(
modifier: Modifier = Modifier,
category: Category,
onClick: (Category) -> Unit,
) {
Card(
modifier = modifier then Modifier
.padding(vertical = PADDING_4)
.fillMaxWidth()
.defaultMinSize(minHeight = MEDIUM_INPUT_HEIGHT)
.clickable { onClick(category) },
backgroundColor = MaterialTheme.colors.surface,
shape = RoundedCornerShape(corner = CornerSize(CORNER_RADIUS_4)),
) {
Row(
modifier = Modifier.padding(PADDING_8),
verticalAlignment = Alignment.CenterVertically,
) {

Box(
modifier = Modifier
.width(MIN_INPUT_WIDTH)
.height(MIN_INPUT_HEIGHT)
.background(
category.secondaryColor.copy(alpha = 0.2f),
RoundedCornerShape(CORNER_RADIUS_4)
),
contentAlignment = Alignment.Center,
) {
Image(
painter = painterResource(category.icon),
contentDescription = category.categoryName,
colorFilter = ColorFilter.tint(category.primaryColor),
)
}

Text(
modifier = Modifier.padding(PADDING_8),
text = category.categoryName,
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package presentation.di

import org.koin.dsl.module
import presentation.screen.balance.BalanceViewModel
import presentation.screen.category.CategoryViewModel
import presentation.screen.home.HomeViewModel
import presentation.screen.operation.NewOperationViewModel

val viewModelModule = module {
factory { HomeViewModel() }
factory { NewOperationViewModel(get()) }
factory { BalanceViewModel(get()) }
factory { CategoryViewModel() }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package presentation.event

import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow

class EventManager<T>(initialEvent: T) {
private val _events = MutableStateFlow(initialEvent)
val events: StateFlow<T> = _events.asStateFlow()

fun emitEvent(event: T) {
_events.value = event
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package presentation.navigation
object Route {
const val BALANCE = "/balance"
const val HOME = "/home"
const val GROUP_NEW_OPERATION = "/group_new_operation"
const val NEW_OPERATION = "/new_operation"
const val CATEGORY = "/category"
}

Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package presentation.navigation

import androidx.compose.runtime.Composable
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import domain.model.Category
import moe.tlaster.precompose.navigation.NavHost
import moe.tlaster.precompose.navigation.NavOptions
import moe.tlaster.precompose.navigation.PopUpTo
import moe.tlaster.precompose.navigation.rememberNavigator
import presentation.screen.balance.BalanceRoute
import presentation.screen.category.CategoryRoute
import presentation.screen.home.HomeRoute
import presentation.screen.operation.NewOperationRoute

Expand All @@ -23,11 +25,30 @@ fun navHost(modifier: Modifier = Modifier, initialRoute: String) {
}

scene(route = Route.HOME) {
HomeRoute(onNewOperation = { nav.navigate(Route.NEW_OPERATION) })
HomeRoute(onNewOperation = { nav.navigate(Route.GROUP_NEW_OPERATION) })
}

scene(route = Route.NEW_OPERATION) {
NewOperationRoute(onBack = { nav.goBack() })
group(route = Route.GROUP_NEW_OPERATION, initialRoute = Route.NEW_OPERATION) {
val currentCategory = mutableStateOf(Category.default())

scene(route = Route.NEW_OPERATION) {
NewOperationRoute(
currentCategory = currentCategory.value,
onBack = { nav.goBack() },
onSelectCategory = { nav.navigate(Route.CATEGORY) },
)
}

scene(route = Route.CATEGORY) {
CategoryRoute(
onCategory = { category ->
currentCategory.value = category
nav.goBack()
},
onBack = { nav.goBack() },
)
}
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package presentation.screen.category

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Surface
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import domain.model.Category
import domain.model.UIState
import moe.tlaster.precompose.flow.collectAsStateWithLifecycle
import moe.tlaster.precompose.koin.koinViewModel
import org.jetbrains.compose.ui.tooling.preview.Preview
import presentation.designsystem.CategoryCard
import presentation.designsystem.SearchInputText
import presentation.designsystem.TopAppBar
import presentation.theme.MoneyMateTheme
import presentation.theme.PADDING_16
import presentation.theme.PADDING_24
import presentation.theme.PADDING_8


@Composable
fun CategoryRoute(
modifier: Modifier = Modifier,
onCategory: (category: Category) -> Unit,
onBack: () -> Unit,
) {
val viewModel = koinViewModel(CategoryViewModel::class)
val categories by viewModel.categories.collectAsStateWithLifecycle()

CategoryScreen(
modifier = modifier.fillMaxSize().padding(horizontal = PADDING_24),
uiState = categories,
onQuery = viewModel::queryCategories,
onCategory = onCategory,
onBack = onBack,
)
}

@Composable
internal fun CategoryScreen(
modifier: Modifier = Modifier,
uiState: UIState<Array<Category>>,
onQuery: (String) -> Unit,
onCategory: (category: Category) -> Unit,
onBack: () -> Unit,
) {
var query by remember { mutableStateOf("") }

Scaffold(
topBar = {
TopAppBar(title = "Categories", onBack = onBack)
},
) { paddingValues ->
LazyColumn(modifier = modifier then Modifier.padding(paddingValues)) {
item {
SearchInputText(
modifier = Modifier.padding(top = PADDING_8, bottom = PADDING_16),
value = query,
label = "Search category...",
onValueChange = { query = it; onQuery(it) },
)
}

when (uiState) {
is UIState.Success -> {
items(items = uiState.result) { category ->
CategoryCard(
category = category,
onClick = { onCategory(category) },
)
}
}
else -> Unit
}

}
}
}

@Composable
@Preview
private fun PreviewCategoryScreen() {
MoneyMateTheme {
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
CategoryScreen(
modifier = Modifier.fillMaxSize().padding(horizontal = PADDING_24),
uiState = UIState.Empty,
onQuery = { },
onCategory = { },
onBack = { },
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package presentation.screen.category

import domain.model.Category
import domain.model.UIState
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import presentation.base.BaseViewModel

class CategoryViewModel : BaseViewModel() {
private val _categories = MutableStateFlow<UIState<Array<Category>>>(UIState.Success(Category.all()))
val categories: StateFlow<UIState<Array<Category>>> = _categories.asStateFlow()

fun queryCategories(query: String) {
_categories.value = UIState.Success(Category.filterCategory(query))
}
}
Loading

0 comments on commit 12b2f1b

Please sign in to comment.