Merge remote-tracking branch 'origin/Florian'

# Conflicts:
#	Ledger/app/src/main/java/at/xaxa/ledger/ui/category/add/AddCategoryUI.kt
#	Ledger/app/src/main/java/at/xaxa/ledger/ui/entry/add/AddUI.kt
#	Ledger/app/src/main/java/at/xaxa/ledger/ui/entry/edit/EditUI.kt
This commit is contained in:
Florian 2025-01-20 10:13:26 +01:00
commit 2da0e0ac40
7 changed files with 127 additions and 30 deletions

View File

@ -7,6 +7,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuBox
@ -55,8 +57,8 @@ fun AddCategory(
value = name, value = name,
onValueChange = { name = it }, onValueChange = { name = it },
label = { Text("Category Name") }, label = { Text("Category Name") },
modifier = Modifier modifier = Modifier.fillMaxWidth().verticalScroll(rememberScrollState()),
.fillMaxWidth() maxLines = 3,
) )
ExposedDropdownMenuBox( ExposedDropdownMenuBox(

View File

@ -6,6 +6,8 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Info
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
@ -106,9 +108,9 @@ fun EditCategory(
OutlinedTextField( OutlinedTextField(
value = category.categoryName, value = category.categoryName,
onValueChange = { editCategoryViewModel.updateCategory(category.copy(categoryName = it)) }, onValueChange = { editCategoryViewModel.updateCategory(category.copy(categoryName = it)) },
modifier = Modifier.fillMaxWidth().verticalScroll(rememberScrollState()),
maxLines = 3,
label = { Text("Category Name") }, label = { Text("Category Name") },
modifier = Modifier
.fillMaxWidth()
) )
ExposedDropdownMenuBox( ExposedDropdownMenuBox(

View File

@ -65,6 +65,5 @@ class EditCategoryViewModel(private val savedStateHandle: SavedStateHandle, priv
redirect= true; redirect= true;
} }
} }
//print("ASASDADS$categoryId")
} }
} }

View File

@ -2,17 +2,21 @@ package at.xaxa.ledger.ui.category.overview
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import at.xaxa.ledger.ui.AppViewModelProvider import at.xaxa.ledger.ui.AppViewModelProvider
import at.xaxa.ledger.ui.ButtonSuccess import at.xaxa.ledger.ui.ButtonSuccess
@ -24,8 +28,7 @@ fun CategoryOverview(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onCardClick: (Int) -> Unit, onCardClick: (Int) -> Unit,
categoryViewModel: OverviewCategoryViewModel = viewModel(factory = AppViewModelProvider.Factory) categoryViewModel: OverviewCategoryViewModel = viewModel(factory = AppViewModelProvider.Factory)
){ ) {
val categories by categoryViewModel.categoryUiState.categories.collectAsState(initial = emptyList()) val categories by categoryViewModel.categoryUiState.categories.collectAsState(initial = emptyList())
Column( Column(
@ -34,6 +37,16 @@ fun CategoryOverview(
.padding(16.dp, 0.dp), .padding(16.dp, 0.dp),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
// Headline: "Your Categories"
Text(
text = "Your Categories",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(vertical = 16.dp)
.align(Alignment.Start)
)
LazyColumn( LazyColumn(
Modifier.weight(1f) Modifier.weight(1f)
) { ) {

View File

@ -3,9 +3,12 @@ package at.xaxa.ledger.ui.entry.add
import android.util.Log import android.util.Log
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Info
@ -16,6 +19,7 @@ import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -37,6 +41,7 @@ import at.xaxa.ledger.ui.ButtonSuccess
import at.xaxa.ledger.ui.DatePickerDocked import at.xaxa.ledger.ui.DatePickerDocked
import at.xaxa.ledger.ui.category.iconNames import at.xaxa.ledger.ui.category.iconNames
import at.xaxa.ledger.ui.category.icons import at.xaxa.ledger.ui.category.icons
import kotlin.math.abs
import kotlin.math.log import kotlin.math.log
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@ -52,10 +57,11 @@ fun Add(
var spending by remember { mutableStateOf("") } var spending by remember { mutableStateOf("") }
var selectedDate by remember { mutableLongStateOf(System.currentTimeMillis()) } var selectedDate by remember { mutableLongStateOf(System.currentTimeMillis()) }
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
var selectedIconIndex by remember { mutableStateOf(0) } // Store index of selected icon var selectedIconIndex by remember { mutableStateOf(0) }
var selectedCategoryName by remember { mutableStateOf("") } // Store index of selected icon var selectedCategoryName by remember { mutableStateOf("") }
var selectedCategory by remember { mutableIntStateOf(-1) } var selectedCategory by remember { mutableIntStateOf(-1) }
var parsingError by remember { mutableStateOf(false) } var parsingError by remember { mutableStateOf(false) }
var isIncome by remember { mutableStateOf(true) } // true for Income, false for Expense
if (parsingError) { if (parsingError) {
AlertDialog( AlertDialog(
@ -95,14 +101,18 @@ fun Add(
OutlinedTextField( OutlinedTextField(
value = name, value = name,
onValueChange = { name = it }, onValueChange = { name = it },
maxLines = 3,
label = { Text("Name") }, label = { Text("Name") },
modifier = Modifier modifier = Modifier.fillMaxWidth().verticalScroll(rememberScrollState()),
.fillMaxWidth()
) )
// Toggle between Income and Expense
OutlinedTextField( OutlinedTextField(
value = spending, value = spending,
onValueChange = { onValueChange = {
val isValidSpending = it.matches(Regex("^[+-]?\\d*(\\.\\d{0,2})?$")) val isValidSpending = it.matches(Regex("^-?(\\d{0,7})(\\.\\d{0,2})?$"))
if (isValidSpending) { if (isValidSpending) {
spending = it spending = it
} }
@ -118,13 +128,13 @@ fun Add(
onExpandedChange = { expanded = it } onExpandedChange = { expanded = it }
) { ) {
OutlinedTextField( OutlinedTextField(
value = selectedCategoryName, // Show selected icon name value = selectedCategoryName,
onValueChange = {}, onValueChange = {},
label = { Text("Category") }, label = { Text("Category") },
readOnly = true, readOnly = true,
leadingIcon = { leadingIcon = {
Icon( Icon(
imageVector = icons[selectedIconIndex], // Replace with your desired icon imageVector = icons[selectedIconIndex],
contentDescription = "Leading Icon" contentDescription = "Leading Icon"
) )
}, },
@ -142,9 +152,9 @@ fun Add(
) { ) {
categories.forEachIndexed { index, category -> categories.forEachIndexed { index, category ->
DropdownMenuItem( DropdownMenuItem(
text = { Text(text = category.categoryName) }, // Use name from iconNames text = { Text(text = category.categoryName) },
onClick = { onClick = {
selectedIconIndex = category.icon // Update selected index selectedIconIndex = category.icon
expanded = false expanded = false
selectedCategoryName = category.categoryName selectedCategoryName = category.categoryName
selectedCategory = category._id selectedCategory = category._id
@ -163,8 +173,29 @@ fun Add(
DatePickerDocked { dateMilis -> DatePickerDocked { dateMilis ->
selectedDate = dateMilis selectedDate = dateMilis
} }
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(text = if (isIncome) "Income" else "Expense")
Switch(
modifier = Modifier.padding(8.dp, 0.dp),
checked = isIncome,
onCheckedChange = {
isIncome = it
if(isIncome){
spending = abs(spending.toFloat()).toString()
}else{
spending = (abs(spending.toFloat())*-1f).toString()
}
}
)
}
} }
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth(), .fillMaxWidth(),
@ -174,8 +205,7 @@ fun Add(
modifier = Modifier, modifier = Modifier,
"Add Transaction", "Add Transaction",
onClick = { onClick = {
val isValidSpending = spending.matches(Regex("^[+-]?\\d*(\\.\\d{0,2})?$")) val isValidSpending = spending.matches(Regex("^-?\\d*(\\.\\d{0,2})?$"))
if (name.isNotBlank() && isValidSpending && selectedDate != 0L && selectedCategory != -1) { if (name.isNotBlank() && isValidSpending && selectedDate != 0L && selectedCategory != -1) {
val newEntry = Entry( val newEntry = Entry(
id = 0, id = 0,
@ -187,10 +217,10 @@ fun Add(
addViewModel.addEntryToDB(newEntry) addViewModel.addEntryToDB(newEntry)
onCardClick() onCardClick()
} else { } else {
parsingError = true; parsingError = true
} }
} }
) )
} }
} }
} }

View File

@ -2,9 +2,12 @@ package at.xaxa.ledger.ui.entry.edit
import android.util.Log import android.util.Log
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@ -13,6 +16,7 @@ import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.ExposedDropdownMenuDefaults.TrailingIcon import androidx.compose.material3.ExposedDropdownMenuDefaults.TrailingIcon
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -34,6 +38,7 @@ import at.xaxa.ledger.ui.ButtonSuccess
import at.xaxa.ledger.ui.DatePickerDocked import at.xaxa.ledger.ui.DatePickerDocked
import at.xaxa.ledger.ui.category.iconNames import at.xaxa.ledger.ui.category.iconNames
import at.xaxa.ledger.ui.category.icons import at.xaxa.ledger.ui.category.icons
import kotlin.math.abs
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -50,16 +55,16 @@ fun Edit(
var selectedIconIndex by remember { mutableStateOf(category.icon) } var selectedIconIndex by remember { mutableStateOf(category.icon) }
var selectedCategoryName by remember { mutableStateOf(category.categoryName) } var selectedCategoryName by remember { mutableStateOf(category.categoryName) }
var selectedCategory by remember { mutableIntStateOf(category._id) } var selectedCategory by remember { mutableIntStateOf(category._id) }
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
var isIncome by remember { mutableStateOf(entry.amount >= 0) } // true for Income, false for Expense
LaunchedEffect(category) { LaunchedEffect(category) {
selectedIconIndex = category.icon selectedIconIndex = category.icon
selectedCategoryName = category.categoryName selectedCategoryName = category.categoryName
selectedCategory = category._id selectedCategory = category._id
isIncome = (entry.amount >= 0)
} }
Column( Column(
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
@ -71,16 +76,23 @@ fun Edit(
) { ) {
OutlinedTextField( OutlinedTextField(
value = entry.name, value = entry.name,
maxLines = 3,
onValueChange = { editViewModel.updateEntry(entry.copy(name = it)) }, onValueChange = { editViewModel.updateEntry(entry.copy(name = it)) },
label = { Text("Name") }, label = { Text("Name") },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth().verticalScroll(rememberScrollState())
) )
// Toggle between Income and Expense
OutlinedTextField( OutlinedTextField(
value = entry.amount.toString(), value = entry.amount.toString(),
onValueChange = { onValueChange = {
val isValidSpending = it.matches(Regex("^[+-]?\\d*(\\.\\d{0,2})?$")) val isValidSpending = it.matches(Regex("^-?(\\d{0,7})(\\.\\d{0,2})?$"))
if (isValidSpending) { if (isValidSpending) {
editViewModel.updateEntry(entry.copy(amount = it.toFloat())) val amount = if (isIncome) it.toFloat() else -it.toFloat()
editViewModel.updateEntry(entry.copy(amount = amount))
} }
}, },
label = { Text("Amount") }, label = { Text("Amount") },
@ -93,13 +105,13 @@ fun Edit(
onExpandedChange = { expanded = it } onExpandedChange = { expanded = it }
) { ) {
OutlinedTextField( OutlinedTextField(
value = selectedCategoryName, // Show selected category name value = selectedCategoryName,
onValueChange = {}, onValueChange = {},
label = { Text("Category") }, label = { Text("Category") },
readOnly = true, readOnly = true,
leadingIcon = { leadingIcon = {
Icon( Icon(
imageVector = icons[selectedIconIndex], // Show selected icon imageVector = icons[selectedIconIndex],
contentDescription = "Leading Icon" contentDescription = "Leading Icon"
) )
}, },
@ -119,9 +131,9 @@ fun Edit(
DropdownMenuItem( DropdownMenuItem(
text = { Text(text = category.categoryName) }, text = { Text(text = category.categoryName) },
onClick = { onClick = {
selectedIconIndex = category.icon // Update selected icon index selectedIconIndex = category.icon
selectedCategoryName = category.categoryName // Update selected category name selectedCategoryName = category.categoryName
selectedCategory = category._id // Update selected category ID selectedCategory = category._id
expanded = false expanded = false
}, },
leadingIcon = { leadingIcon = {
@ -138,8 +150,30 @@ fun Edit(
DatePickerDocked(entry) { dateMilis -> DatePickerDocked(entry) { dateMilis ->
editViewModel.updateEntry(entry.copy(date = dateMilis)) editViewModel.updateEntry(entry.copy(date = dateMilis))
} }
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(text = if (isIncome) "Income" else "Expense")
Switch(
modifier = Modifier.padding(8.dp, 0.dp),
checked = isIncome,
onCheckedChange = {
isIncome = it
if(isIncome){
editViewModel.updateEntry(entry.copy(amount = abs(entry.amount)))
}else{
editViewModel.updateEntry(entry.copy(amount = abs(entry.amount)*-1f))
}
}
)
}
} }
Column( Column(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
@ -158,6 +192,7 @@ fun Edit(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
text = "Done", text = "Done",
onClick = { onClick = {
editViewModel.updateEntry(entry)
editViewModel.saveEntry() editViewModel.saveEntry()
onCardClick() onCardClick()
} }

View File

@ -10,12 +10,15 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import at.xaxa.ledger.ui.AppViewModelProvider import at.xaxa.ledger.ui.AppViewModelProvider
import at.xaxa.ledger.ui.ButtonSuccess import at.xaxa.ledger.ui.ButtonSuccess
@ -37,18 +40,31 @@ fun Home(modifier: Modifier = Modifier, onCardClick: (Int) -> Unit, onButtonClic
.padding(16.dp, 0.dp), .padding(16.dp, 0.dp),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
LazyColumn( LazyColumn(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
) { ) {
stickyHeader { stickyHeader {
HeaderCard(modifier = modifier, "%.2f".format(balance)+"", onCatButtonClick) HeaderCard(modifier = modifier, "%.2f".format(balance)+"", onCatButtonClick)
Text(
text = "Your Transactions",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(vertical = 16.dp)
.align(Alignment.Start)
)
} }
items(state) { item -> items(state) { item ->
Column( Column(
modifier = Modifier.padding(vertical = 4.dp) modifier = Modifier.padding(vertical = 4.dp)
) { ) {
HorizontalCard( HorizontalCard(
modifier = modifier, modifier = modifier,
name = item.name, name = item.name,