diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_arrow_right.xml b/composeApp/src/commonMain/composeResources/drawable/ic_arrow_right.xml
new file mode 100644
index 0000000..01e27bc
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_arrow_right.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_dining_out.xml b/composeApp/src/commonMain/composeResources/drawable/ic_dining_out.xml
new file mode 100644
index 0000000..0f0a273
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_dining_out.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_education.xml b/composeApp/src/commonMain/composeResources/drawable/ic_education.xml
new file mode 100644
index 0000000..78384bb
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_education.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_entertainment.xml b/composeApp/src/commonMain/composeResources/drawable/ic_entertainment.xml
new file mode 100644
index 0000000..63cd7c3
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_entertainment.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_gifts.xml b/composeApp/src/commonMain/composeResources/drawable/ic_gifts.xml
new file mode 100644
index 0000000..f9fda3b
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_gifts.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_groceries.xml b/composeApp/src/commonMain/composeResources/drawable/ic_groceries.xml
new file mode 100644
index 0000000..570c5a0
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_groceries.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_health.xml b/composeApp/src/commonMain/composeResources/drawable/ic_health.xml
new file mode 100644
index 0000000..5666fe7
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_health.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_income.xml b/composeApp/src/commonMain/composeResources/drawable/ic_income.xml
new file mode 100644
index 0000000..e85f110
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_income.xml
@@ -0,0 +1,6 @@
+
+
+
diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_miscellaneous.xml b/composeApp/src/commonMain/composeResources/drawable/ic_miscellaneous.xml
new file mode 100644
index 0000000..29b7166
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_miscellaneous.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_subscription.xml b/composeApp/src/commonMain/composeResources/drawable/ic_subscription.xml
new file mode 100644
index 0000000..9449779
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_subscription.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_transport.xml b/composeApp/src/commonMain/composeResources/drawable/ic_transport.xml
new file mode 100644
index 0000000..088aab1
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_transport.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_travel.xml b/composeApp/src/commonMain/composeResources/drawable/ic_travel.xml
new file mode 100644
index 0000000..8849886
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_travel.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/composeApp/src/commonMain/kotlin/data/datasource/local/LocalOperationDataSource.kt b/composeApp/src/commonMain/kotlin/data/datasource/local/LocalOperationDataSource.kt
index c157c36..d2ec373 100644
--- a/composeApp/src/commonMain/kotlin/data/datasource/local/LocalOperationDataSource.kt
+++ b/composeApp/src/commonMain/kotlin/data/datasource/local/LocalOperationDataSource.kt
@@ -1,9 +1,10 @@
package data.datasource.local
import com.github.guibrisson.db.MoneyMateDatabase
-import com.github.guibrisson.db.OperationTable
import data.datasource.OperationDataSource
+import domain.model.Category
import domain.model.Operation
+import domain.model.OperationType
import kotlinx.datetime.LocalDateTime
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
@@ -19,9 +20,9 @@ class LocalOperationDataSource: KoinComponent, OperationDataSource {
id = -1,
amount = 0.0,
description = "Test",
- type = "",
- category = "",
- date = LocalDateTime(2024, 3, 6, 1, 1, 1, 1),
+ type = OperationType.INCOME,
+ category = Category.EDUCATION,
+ date = LocalDateTime(2024, 3, 6, 1, 1 ,1, 1),
isPeriodic = false,
)
)
diff --git a/composeApp/src/commonMain/kotlin/domain/model/Category.kt b/composeApp/src/commonMain/kotlin/domain/model/Category.kt
index f1c72b7..d605602 100644
--- a/composeApp/src/commonMain/kotlin/domain/model/Category.kt
+++ b/composeApp/src/commonMain/kotlin/domain/model/Category.kt
@@ -1,4 +1,59 @@
package domain.model
-// TODO: implement properly
-typealias Category = String
+import androidx.compose.ui.graphics.Color
+import moneymate.composeapp.generated.resources.Res
+import moneymate.composeapp.generated.resources.ic_dining_out
+import moneymate.composeapp.generated.resources.ic_education
+import moneymate.composeapp.generated.resources.ic_entertainment
+import moneymate.composeapp.generated.resources.ic_gifts
+import moneymate.composeapp.generated.resources.ic_groceries
+import moneymate.composeapp.generated.resources.ic_health
+import moneymate.composeapp.generated.resources.ic_income
+import moneymate.composeapp.generated.resources.ic_miscellaneous
+import moneymate.composeapp.generated.resources.ic_subscription
+import moneymate.composeapp.generated.resources.ic_transport
+import moneymate.composeapp.generated.resources.ic_travel
+import org.jetbrains.compose.resources.DrawableResource
+import org.jetbrains.compose.resources.ExperimentalResourceApi
+import presentation.theme.amber500
+import presentation.theme.amber600
+import presentation.theme.blue500
+import presentation.theme.blue600
+import presentation.theme.cyan500
+import presentation.theme.cyan600
+import presentation.theme.emerald500
+import presentation.theme.emerald600
+import presentation.theme.indigo500
+import presentation.theme.indigo600
+import presentation.theme.lime500
+import presentation.theme.lime600
+import presentation.theme.orange500
+import presentation.theme.orange600
+import presentation.theme.pink500
+import presentation.theme.pink600
+import presentation.theme.purple500
+import presentation.theme.purple600
+import presentation.theme.red500
+import presentation.theme.red600
+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) {
+ 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),
+ INCOME("Income", lime600, lime500, Res.drawable.ic_income),
+ DINING_OUT("Dining Out", emerald600, emerald500, Res.drawable.ic_dining_out),
+ EDUCATION("Education", cyan600, cyan500, Res.drawable.ic_education),
+ GROCERIES("Groceries", blue600, blue500, Res.drawable.ic_groceries),
+ TRAVEL("Travel", indigo500, indigo600, Res.drawable.ic_travel),
+ TRANSPORT("Transport", purple600, purple500, Res.drawable.ic_transport),
+ GIFTS_DONATIONS("Gifts & Donations", pink600, pink500, Res.drawable.ic_gifts),
+ MISCELLANEOUS("Miscellaneous", rose600, rose500, Res.drawable.ic_miscellaneous);
+
+ companion object {
+ fun all(): Array = enumValues()
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/domain/model/Operation.kt b/composeApp/src/commonMain/kotlin/domain/model/Operation.kt
index df3040a..ea812b2 100644
--- a/composeApp/src/commonMain/kotlin/domain/model/Operation.kt
+++ b/composeApp/src/commonMain/kotlin/domain/model/Operation.kt
@@ -2,12 +2,11 @@ package domain.model
import kotlinx.datetime.LocalDateTime
-
data class Operation(
val id: Long,
val amount: Double,
val description: String,
- val type: OperationType,
+ val type: OperationType,
val category: Category,
val date: LocalDateTime,
val isPeriodic: Boolean,
diff --git a/composeApp/src/commonMain/kotlin/domain/model/OperationType.kt b/composeApp/src/commonMain/kotlin/domain/model/OperationType.kt
index e1033cb..443060a 100644
--- a/composeApp/src/commonMain/kotlin/domain/model/OperationType.kt
+++ b/composeApp/src/commonMain/kotlin/domain/model/OperationType.kt
@@ -1,4 +1,16 @@
package domain.model
-// TODO: implement properly
-typealias OperationType = String // "expense" || "income"
+import androidx.compose.ui.graphics.Color
+import presentation.theme.expense
+import presentation.theme.income
+
+enum class OperationType(val operation: String, val color: Color) {
+ EXPENSE("Expense", expense),
+ INCOME("Income", income);
+
+ companion object {
+ private fun all(): Array = enumValues()
+
+ fun operationNames(): List = all().map { it.operation }
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/presentation/designsystem/AppBars.kt b/composeApp/src/commonMain/kotlin/presentation/designsystem/AppBars.kt
new file mode 100644
index 0000000..eb3064a
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/presentation/designsystem/AppBars.kt
@@ -0,0 +1,136 @@
+package presentation.designsystem
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Tab
+import androidx.compose.material.TabRow
+import androidx.compose.material.TabRowDefaults.tabIndicatorOffset
+import androidx.compose.material.Text
+import androidx.compose.material.TopAppBar
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.zIndex
+import kotlinx.coroutines.launch
+import presentation.theme.CORNER_RADIUS_4
+import presentation.theme.FONT_16
+import presentation.theme.SUPER_SMALL_PADDING
+import presentation.theme.ZERO_DP
+import presentation.theme.gray400
+
+@Composable
+fun TopAppBar(title: String, onBack: () -> Unit) {
+ TopAppBar(
+ contentColor = Color.White,
+ backgroundColor = Color.Transparent,
+ elevation = ZERO_DP,
+ ) {
+ Box(modifier = Modifier
+ .fillMaxSize()
+ .align(Alignment.CenterVertically)
+ ) {
+ Text(
+ modifier = Modifier
+ .fillMaxWidth()
+ .align(Alignment.Center),
+ text = title,
+ fontWeight = FontWeight.Bold,
+ fontSize = FONT_16,
+ textAlign = TextAlign.Center
+ )
+ IconButton(
+ modifier = Modifier
+ .align(Alignment.CenterStart)
+ .fillMaxHeight(),
+ onClick = { onBack.invoke() }
+ ) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Filled.ArrowBack,
+ contentDescription = "Back Button"
+ )
+ }
+ }
+ }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+fun TabBar(modifier: Modifier = Modifier, pages: List,
+ contentColors: List, onTabBarChange: (String) -> Unit) {
+
+ val pagerState = rememberPagerState(pageCount = { pages.size })
+ val coroutineScope = rememberCoroutineScope()
+ var selectedTabIndex by remember {
+ mutableStateOf(0)
+ }
+
+ TabRow(
+ modifier = modifier
+ .clip(shape = RoundedCornerShape(CORNER_RADIUS_4)),
+ selectedTabIndex = selectedTabIndex,
+ indicator = { tabPositions ->
+ Box(
+ modifier = Modifier
+ .tabIndicatorOffset(tabPositions[selectedTabIndex])
+ .zIndex(-1f)
+ .fillMaxWidth()
+ .fillMaxHeight()
+ .padding(SUPER_SMALL_PADDING)
+ .background(
+ color = contentColors[selectedTabIndex].copy(alpha = 0.12f),
+ shape = RoundedCornerShape(CORNER_RADIUS_4)
+ )
+ )
+ },
+ contentColor = MaterialTheme.colors.secondary,
+ divider = {}
+ ) {
+ pages.forEachIndexed { index, title ->
+ Tab(
+ text = {
+ Text(
+ modifier = Modifier
+ .semantics { contentDescription = "Tab Title" },
+ text = title,
+ color =
+ if (selectedTabIndex == index)
+ contentColors[selectedTabIndex]
+ else
+ gray400,
+ fontWeight = FontWeight.Bold
+ )
+ },
+ selected = pagerState.currentPage == index,
+ onClick = {
+ coroutineScope.launch {
+ selectedTabIndex = index
+ onTabBarChange(pages[selectedTabIndex])
+ }
+ },
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/presentation/designsystem/InputText.kt b/composeApp/src/commonMain/kotlin/presentation/designsystem/InputText.kt
index 05f5dec..349f7d4 100644
--- a/composeApp/src/commonMain/kotlin/presentation/designsystem/InputText.kt
+++ b/composeApp/src/commonMain/kotlin/presentation/designsystem/InputText.kt
@@ -6,7 +6,12 @@ import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
@@ -30,7 +35,10 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
+import presentation.theme.FONT_32
+import presentation.theme.FONT_48
+import presentation.theme.INPUT_HEIGHT
+import presentation.theme.MIN_INPUT_HEIGHT
import presentation.theme.oswaldFontFamily
import utils.MoneyVisualTransformation
import utils.canBeLong
@@ -70,7 +78,7 @@ private fun BaseInputText(
) { innerTextField ->
Row(
modifier = Modifier
- .defaultMinSize(minHeight = 48.dp)
+ .defaultMinSize(minHeight = MIN_INPUT_HEIGHT)
.clip(MaterialTheme.shapes.small)
.background(MaterialTheme.colors.surface)
.padding(12.dp),
@@ -125,7 +133,7 @@ fun SearchInputText(
BaseInputText(
value = value,
onValueChange = onValueChange,
- modifier = modifier.height(52.dp),
+ modifier = modifier.height(INPUT_HEIGHT),
singleLine = true,
label = searchLabel,
leadingIcon = leadingIcon,
@@ -150,7 +158,7 @@ fun InputText(
BaseInputText(
value = value,
onValueChange = onValueChange,
- modifier = modifier.height(52.dp),
+ modifier = modifier.height(INPUT_HEIGHT),
singleLine = true,
label = basicLabel,
)
@@ -172,15 +180,15 @@ fun MoneyInputText(
Text(
text = "$",
fontFamily = oswaldFontFamily(),
- fontSize = 32.sp,
+ fontSize = FONT_32,
fontWeight = FontWeight.Light,
)
val textStyle = TextStyle(
- fontSize = 48.sp,
+ fontSize = FONT_48,
fontFamily = oswaldFontFamily(),
color = MaterialTheme.colors.onBackground,
- lineHeight = 48.sp,
+ lineHeight = FONT_48,
)
BasicTextField(
diff --git a/composeApp/src/commonMain/kotlin/presentation/screen/operation/NewOperationScreen.kt b/composeApp/src/commonMain/kotlin/presentation/screen/operation/NewOperationScreen.kt
index 40a2844..175fd26 100644
--- a/composeApp/src/commonMain/kotlin/presentation/screen/operation/NewOperationScreen.kt
+++ b/composeApp/src/commonMain/kotlin/presentation/screen/operation/NewOperationScreen.kt
@@ -1,48 +1,278 @@
package presentation.screen.operation
-import androidx.compose.foundation.layout.*
-import androidx.compose.material.*
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.rounded.ArrowBack
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+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.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.Icon
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Scaffold
+import androidx.compose.material.Surface
+import androidx.compose.material.Switch
+import androidx.compose.material.SwitchDefaults
+import androidx.compose.material.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+import domain.model.Category
+import domain.model.OperationType
import moe.tlaster.precompose.koin.koinViewModel
+import moneymate.composeapp.generated.resources.Res
+import moneymate.composeapp.generated.resources.ic_arrow_right
+import org.jetbrains.compose.resources.DrawableResource
+import org.jetbrains.compose.resources.ExperimentalResourceApi
+import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.ui.tooling.preview.Preview
+import presentation.designsystem.InputText
+import presentation.designsystem.MoneyInputText
+import presentation.designsystem.PrimaryButton
+import presentation.designsystem.TabBar
+import presentation.designsystem.TopAppBar
+import presentation.theme.EXTRA_SMALL_PADDING
+import presentation.theme.FONT_14
+import presentation.theme.FONT_16
+import presentation.theme.INPUT_HEIGHT
+import presentation.theme.LARGE_PADDING
+import presentation.theme.MEDIUM_PADDING
import presentation.theme.MoneyMateTheme
+import presentation.theme.SMALL_PADDING
+import presentation.theme.border
+import presentation.theme.expense
+import presentation.theme.income
@Composable
fun NewOperationRoute(
- modifier: Modifier = Modifier,
onBack: () -> Unit,
) {
val viewModel = koinViewModel(NewOperationViewModel::class)
- NewOperationScreen(modifier = modifier, onBack = onBack)
+ NewOperationScreen(onBack = onBack)
}
+@OptIn(ExperimentalResourceApi::class)
@Composable
internal fun NewOperationScreen(
- modifier: Modifier = Modifier,
onBack: () -> Unit,
) {
- Column(modifier = modifier.fillMaxSize().padding(20.dp)) {
- Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
- IconButton(modifier = Modifier.offset(x = (-16).dp), onClick = onBack) {
- Icon(Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = "Arrow back")
+ var operationAmount by remember {
+ mutableStateOf("")
+ }
+
+ var description by remember {
+ mutableStateOf("")
+ }
+
+ var category: Category? by rememberSaveable {
+ mutableStateOf(Category.DINING_OUT)
+ }
+
+ var selectedOperation by rememberSaveable {
+ mutableStateOf(OperationType.EXPENSE)
+ }
+
+ var isMonthlyOperation by rememberSaveable {
+ mutableStateOf(false)
+ }
+
+ val tabContentColors = listOf(expense, income)
+ val operations = OperationType.operationNames().toList()
+
+ //TODO: Remover esta lista ao implementar lógica de navegação para tela de categorias
+ val categories = Category.all()
+
+ Scaffold(
+ topBar = {
+ TopAppBar(title = "New Operation", onBack = onBack)
+ }
+ ) {
+ Column(
+ modifier = Modifier
+ .padding(horizontal = LARGE_PADDING)
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState())
+ ) {
+ TabBar(
+ modifier = Modifier
+ .padding(vertical = LARGE_PADDING),
+ pages = operations,
+ contentColors = tabContentColors
+ ) { selectedItem ->
+ selectedOperation = OperationType.valueOf(selectedItem.uppercase())
+ }
+
+ Text("Add ${selectedOperation.operation.lowercase()}")
+
+ MoneyInputText(
+ value = operationAmount,
+ onValueChange = { operationAmount = it }
+ )
+
+ Text(modifier = Modifier
+ .padding(top = LARGE_PADDING),
+ text = "Description")
+
+ InputText(
+ modifier = Modifier
+ .padding(top = EXTRA_SMALL_PADDING),
+ value = description,
+ label = "e.g Hamburger from Bobs",
+ onValueChange = {
+ description = it
+ }
+ )
+
+ Text(
+ modifier = Modifier
+ .padding(top = LARGE_PADDING),
+ text = "Category"
+ )
+
+ Selector(
+ modifier = Modifier
+ .padding(top = EXTRA_SMALL_PADDING)
+ .fillMaxWidth(),
+ icon = category?.icon,
+ iconTint = category?.primaryColor,
+ value = category?.categoryName,
+ label = "category"
+ ) {
+ //TODO: Call Categories Screen
+ //Remover este código ao implementar navegação para tela de categorias
+ //OBS: Tela de categorias precisa ter um callback para atualizar a category
+ //desta tela. Remover também a lista categories
+ val randomInt = (0..9).random()
+ category = categories[randomInt]
+ }
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = MEDIUM_PADDING),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ){
+ Text(text = "Is this a monthly operation?")
+
+ CustomSwitch {
+ isMonthlyOperation = it
+ }
+ }
+
+ Spacer(Modifier.weight(1f))
+
+ val enabled = operationAmount.isNotEmpty() &&
+ description.isNotEmpty() && category != null
+
+ PrimaryButton(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(bottom = MEDIUM_PADDING),
+ onClick = { },
+ enabled = enabled,
+ ) {
+ Text(text = "Done", style = MaterialTheme.typography.button)
}
+ }
+ }
+}
+
+@OptIn(ExperimentalResourceApi::class)
+@Composable
+fun Selector(modifier: Modifier = Modifier, icon: DrawableResource? = null,
+ iconTint: Color? = null, value: String? = null, label: String,
+ onSelectorClick: () -> Unit
+) {
+ Row(
+ modifier = modifier
+ .height(INPUT_HEIGHT)
+ .clip(MaterialTheme.shapes.small)
+ .background(MaterialTheme.colors.surface)
+ .clickable { onSelectorClick() }
+ .padding(SMALL_PADDING),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(EXTRA_SMALL_PADDING)
+ ) {
+ icon?.let {
+ Icon(
+ painter = painterResource(it),
+ contentDescription = "Category Icon",
+ tint = iconTint ?: Color.White
+ )
+ }
- Text(text = "New operation")
+ value?.let {
+ Text(
+ text = it,
+ fontSize = FONT_16
+ )
}
+
+ Spacer(Modifier.weight(1f))
+
+ Text(
+ text = "Select a $label",
+ fontSize = FONT_14,
+ color = Color.White.copy(alpha = 0.6f)
+ )
+
+ Icon(
+ painterResource(Res.drawable.ic_arrow_right),
+ contentDescription = null
+ )
}
}
+@Composable
+fun CustomSwitch(onChecked: (Boolean) -> Unit) {
+
+ var isChecked by rememberSaveable {
+ mutableStateOf(false)
+ }
+
+ Switch(
+ modifier = Modifier
+ .semantics {
+ contentDescription = "True or false selector"
+ },
+ checked = isChecked,
+ onCheckedChange = { checked ->
+ isChecked = checked
+ onChecked(isChecked)
+ },
+ colors = SwitchDefaults.colors(
+ checkedThumbColor = MaterialTheme.colors.primary,
+ checkedTrackColor = MaterialTheme.colors.surface,
+ uncheckedThumbColor = border,
+ uncheckedTrackColor = MaterialTheme.colors.surface
+ )
+ )
+}
+
@Composable
@Preview
private fun PreviewNewOperationScreen() {
MoneyMateTheme {
- Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
+ Surface(
+ modifier = Modifier.fillMaxSize(),
+ color = MaterialTheme.colors.background
+ ) {
NewOperationScreen(onBack = { })
}
}
-}
+}
diff --git a/composeApp/src/commonMain/kotlin/presentation/theme/Colors.kt b/composeApp/src/commonMain/kotlin/presentation/theme/Colors.kt
index ea1be9a..9778f66 100644
--- a/composeApp/src/commonMain/kotlin/presentation/theme/Colors.kt
+++ b/composeApp/src/commonMain/kotlin/presentation/theme/Colors.kt
@@ -46,3 +46,6 @@ val pink500 = Color(0xffec4899)
val pink600 = Color(0xffdb2777)
val rose500 = Color(0xfff43f5e)
val rose600 = Color(0xffe11d48)
+val gray400 = Color(0xFFBBBBBB)
+val expense = Color(0xFFEF7457)
+val income = Color(0xFF26E0A9)
diff --git a/composeApp/src/commonMain/kotlin/presentation/theme/Dimens.kt b/composeApp/src/commonMain/kotlin/presentation/theme/Dimens.kt
new file mode 100644
index 0000000..2e33d50
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/presentation/theme/Dimens.kt
@@ -0,0 +1,21 @@
+package presentation.theme
+
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+val LARGE_PADDING = 24.dp
+val MEDIUM_PADDING = 16.dp
+val SMALL_PADDING = 12.dp
+val EXTRA_SMALL_PADDING = 8.dp
+val SUPER_SMALL_PADDING = 2.dp
+val ZERO_DP = 0.dp
+
+val CORNER_RADIUS_4 = 4.dp
+
+val FONT_14 = 14.sp
+val FONT_16 = 16.sp
+val FONT_32 = 32.sp
+val FONT_48 = 48.sp
+
+val MIN_INPUT_HEIGHT = 48.dp
+val INPUT_HEIGHT = 52.dp
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/presentation/theme/Type.kt b/composeApp/src/commonMain/kotlin/presentation/theme/Type.kt
index b9fcc11..d19f0c5 100644
--- a/composeApp/src/commonMain/kotlin/presentation/theme/Type.kt
+++ b/composeApp/src/commonMain/kotlin/presentation/theme/Type.kt
@@ -1,16 +1,21 @@
package presentation.theme
-import androidx.compose.runtime.Composable
import androidx.compose.material.Typography
+import androidx.compose.runtime.Composable
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.unit.sp
-import moneymate.composeapp.generated.resources.*
import moneymate.composeapp.generated.resources.Res
+import moneymate.composeapp.generated.resources.inter_bold
import moneymate.composeapp.generated.resources.inter_light
import moneymate.composeapp.generated.resources.inter_medium
import moneymate.composeapp.generated.resources.inter_regular
+import moneymate.composeapp.generated.resources.inter_semibold
+import moneymate.composeapp.generated.resources.oswald_bold
+import moneymate.composeapp.generated.resources.oswald_light
+import moneymate.composeapp.generated.resources.oswald_medium
+import moneymate.composeapp.generated.resources.oswald_regular
+import moneymate.composeapp.generated.resources.oswald_semibold
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.Font
@@ -40,8 +45,8 @@ fun getTypography(): Typography {
defaultFontFamily = interFontFamily(),
button = TextStyle(
fontWeight = FontWeight.Medium,
- fontSize = 14.sp,
- lineHeight = 16.sp,
+ fontSize = FONT_14,
+ lineHeight = FONT_16,
)
)
}
diff --git a/composeApp/src/commonTest/kotlin/presentation/designsystem/CustomSwitchTest.kt b/composeApp/src/commonTest/kotlin/presentation/designsystem/CustomSwitchTest.kt
new file mode 100644
index 0000000..3521acb
--- /dev/null
+++ b/composeApp/src/commonTest/kotlin/presentation/designsystem/CustomSwitchTest.kt
@@ -0,0 +1,30 @@
+package presentation.designsystem
+
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.runComposeUiTest
+import presentation.screen.operation.CustomSwitch
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class CustomSwitchTest {
+ private val contentDescription = "True or false selector"
+
+ @OptIn(ExperimentalTestApi::class)
+ @Test
+ fun `Assert switch is true or false when perform two clicks`() = runComposeUiTest {
+ var selected = false
+ setContent {
+ CustomSwitch {
+ selected = it
+ }
+ }
+
+ onNodeWithContentDescription(contentDescription).assertExists()
+ onNodeWithContentDescription(contentDescription).performClick()
+ assertEquals(selected, true)
+ onNodeWithContentDescription(contentDescription).performClick()
+ assertEquals(selected, false)
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonTest/kotlin/presentation/designsystem/TabBarTest.kt b/composeApp/src/commonTest/kotlin/presentation/designsystem/TabBarTest.kt
new file mode 100644
index 0000000..ec766ae
--- /dev/null
+++ b/composeApp/src/commonTest/kotlin/presentation/designsystem/TabBarTest.kt
@@ -0,0 +1,48 @@
+package presentation.designsystem
+
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.assertTextEquals
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.runComposeUiTest
+import domain.model.OperationType
+import presentation.theme.expense
+import presentation.theme.income
+import kotlin.test.Test
+
+class TabBarTest {
+
+ private val tabContentColors = listOf(expense, income)
+ private val tabItems = OperationType.operationNames()
+
+ @OptIn(ExperimentalTestApi::class)
+ @Test
+ fun `Perform click on first tab item`() = runComposeUiTest {
+ var selectedItem = ""
+
+ setContent {
+ TabBar(
+ pages = tabItems,
+ contentColors = tabContentColors
+ ) { selectedItem = it }
+ }
+ onNodeWithText(tabItems[0]).performClick()
+ onNodeWithText(tabItems[0]).assertTextEquals(selectedItem)
+ }
+
+ @OptIn(ExperimentalTestApi::class)
+ @Test
+ fun `Perform click on second tab item`() = runComposeUiTest {
+ var selectedItem = ""
+
+ setContent {
+
+ TabBar(
+ pages = tabItems,
+ contentColors = tabContentColors
+ ) { selectedItem = it }
+ }
+ onNodeWithText(tabItems[1]).performClick()
+ onNodeWithText(tabItems[1]).assertTextEquals(selectedItem)
+ }
+}
\ No newline at end of file