From 463e1a013f6c8046e13cf3493160805a6c67c53a Mon Sep 17 00:00:00 2001 From: "Maurice L." Date: Wed, 4 Mar 2026 16:38:40 +0100 Subject: [PATCH] Improved UI and enhanced expense creation Updated the UI across all screens (Login, Dashboard, Account Selection, Expense Details, and Creation) with a more modern, card-based design and improved layouts. Expense creation now includes: - Real-time split validation to ensure shares match the total amount. - An "Equal Split" shortcut for quick allocation. - Support for optional descriptions and multiple file attachments. - Strict save validation. The `MainActivity` now supports edge-to-edge display, and the `AccountDao` includes a new query to fetch accounts by ID. Added `material-icons-extended` dependency for enhanced iconography. --- app/build.gradle.kts | 1 + .../shap_planner/activities/MainActivity.kt | 2 + .../shap_planner/entities/Account.kt | 3 + .../repository/ExpenseShareRepository.kt | 1 - .../miaurizius/shap_planner/ui/AppContent.kt | 4 +- .../ui/screens/AccountSelectionScreen.kt | 179 +++++++++--- .../ui/screens/DashboardScreen.kt | 243 +++++++++------- .../ui/screens/ExpenseCreationScreen.kt | 260 ++++++++++++++---- .../ui/screens/ExpenseDetailScreen.kt | 138 ++++++++-- .../shap_planner/ui/screens/LoginScreen.kt | 180 +++++++++--- .../viewmodels/ExpenseCreationViewModel.kt | 62 +++-- .../shap_planner/viewmodels/MainViewModel.kt | 4 - 12 files changed, 793 insertions(+), 284 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fab25da..99c44fa 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -61,6 +61,7 @@ dependencies { //Manually added implementation("androidx.datastore:datastore-preferences:1.2.0") + implementation("androidx.compose.material:material-icons-extended:1.6.0") val room_version = "2.8.4" implementation("androidx.room:room-runtime:$room_version") diff --git a/app/src/main/java/de/miaurizius/shap_planner/activities/MainActivity.kt b/app/src/main/java/de/miaurizius/shap_planner/activities/MainActivity.kt index 2588d13..c0d2dd3 100644 --- a/app/src/main/java/de/miaurizius/shap_planner/activities/MainActivity.kt +++ b/app/src/main/java/de/miaurizius/shap_planner/activities/MainActivity.kt @@ -4,6 +4,7 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.BackHandler import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -21,6 +22,7 @@ import de.miaurizius.shap_planner.viewmodels.MainViewModel class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() super.onCreate(savedInstanceState) val prefs = UserPreferences(this) diff --git a/app/src/main/java/de/miaurizius/shap_planner/entities/Account.kt b/app/src/main/java/de/miaurizius/shap_planner/entities/Account.kt index e380e86..2f14737 100644 --- a/app/src/main/java/de/miaurizius/shap_planner/entities/Account.kt +++ b/app/src/main/java/de/miaurizius/shap_planner/entities/Account.kt @@ -25,6 +25,9 @@ interface AccountDao { @Query("SELECT * FROM accounts") fun getAllAccounts(): Flow> + @Query("SELECT * FROM accounts WHERE id = :userId") + fun getAccountById(userId: UUID): Flow + @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertAccount(account: Account) diff --git a/app/src/main/java/de/miaurizius/shap_planner/repository/ExpenseShareRepository.kt b/app/src/main/java/de/miaurizius/shap_planner/repository/ExpenseShareRepository.kt index b36b56c..bec3be1 100644 --- a/app/src/main/java/de/miaurizius/shap_planner/repository/ExpenseShareRepository.kt +++ b/app/src/main/java/de/miaurizius/shap_planner/repository/ExpenseShareRepository.kt @@ -1,6 +1,5 @@ package de.miaurizius.shap_planner.repository -import de.miaurizius.shap_planner.entities.Expense import de.miaurizius.shap_planner.entities.ExpenseShare import de.miaurizius.shap_planner.entities.ExpenseShareDao import de.miaurizius.shap_planner.network.APIService diff --git a/app/src/main/java/de/miaurizius/shap_planner/ui/AppContent.kt b/app/src/main/java/de/miaurizius/shap_planner/ui/AppContent.kt index ef80c3a..9cbf4e4 100644 --- a/app/src/main/java/de/miaurizius/shap_planner/ui/AppContent.kt +++ b/app/src/main/java/de/miaurizius/shap_planner/ui/AppContent.kt @@ -1,7 +1,5 @@ package de.miaurizius.shap_planner.ui -import androidx.activity.compose.BackHandler -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -55,7 +53,7 @@ fun AppContent( account = selectedAccount!!, viewModel = creationViewModel, onBack = { showAddExpenseScreen = false }, - onSaved = { showAddExpenseScreen = false } + onSaved = { showAddExpenseScreen = false }, ) } selectedExpense != null -> { diff --git a/app/src/main/java/de/miaurizius/shap_planner/ui/screens/AccountSelectionScreen.kt b/app/src/main/java/de/miaurizius/shap_planner/ui/screens/AccountSelectionScreen.kt index 34eee30..94f8e05 100644 --- a/app/src/main/java/de/miaurizius/shap_planner/ui/screens/AccountSelectionScreen.kt +++ b/app/src/main/java/de/miaurizius/shap_planner/ui/screens/AccountSelectionScreen.kt @@ -1,10 +1,10 @@ package de.miaurizius.shap_planner.ui.screens -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -13,59 +13,176 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Home import androidx.compose.material3.Button -import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import de.miaurizius.shap_planner.entities.Account @OptIn(ExperimentalMaterial3Api::class) @Composable -fun AccountSelectionScreen(accounts: List, onAccountClick: (Account) -> Unit, onAddAccountClick: () -> Unit) { - LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(16.dp) - .statusBarsPadding() - .navigationBarsPadding(), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - item { - Text("Choose an account", style = MaterialTheme.typography.headlineSmall) - } - - items(accounts) { account -> - Card(modifier = Modifier.fillMaxWidth().clickable{ onAccountClick(account) }) { - Row(modifier = Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically) { - Box(modifier = Modifier.size(40.dp).background(Color.Gray, shape = CircleShape)) - Spacer(modifier = Modifier.width(16.dp)) - Column { - Text(text = account.name, fontWeight = FontWeight.Bold) - Text(text = account.wgName, style = MaterialTheme.typography.bodyMedium) - } +fun AccountSelectionScreen( + accounts: List, + onAccountClick: (Account) -> Unit, + onAddAccountClick: () -> Unit +) { + Scaffold( + topBar = { + CenterAlignedTopAppBar( + title = { Text("ShAp-Planner", fontWeight = FontWeight.Black) } + ) + }, + bottomBar = { + Surface( + modifier = Modifier.fillMaxWidth(), + tonalElevation = 2.dp + ) { + Button( + onClick = onAddAccountClick, + modifier = Modifier + .fillMaxWidth() + .padding(20.dp) + .navigationBarsPadding() + .height(56.dp), + shape = MaterialTheme.shapes.medium + ) { + Icon(Icons.Default.Add, contentDescription = null) + Spacer(modifier = Modifier.width(8.dp)) + Text("Add New Account", style = MaterialTheme.typography.titleMedium) } } } - item { - Spacer(modifier = Modifier.height(8.dp)) - Button( - onClick = onAddAccountClick, - modifier = Modifier.fillMaxWidth() + ) { padding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .padding(horizontal = 20.dp) + ) { + Spacer(modifier = Modifier.height(16.dp)) + + Text( + text = "Welcome back!", + style = MaterialTheme.typography.headlineLarge, + fontWeight = FontWeight.Bold + ) + Text( + text = "Select an account to continue", + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.secondary, + modifier = Modifier.padding(bottom = 16.dp) + ) + + LazyColumn( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(12.dp), + contentPadding = PaddingValues(bottom = 16.dp) ) { - Text("Add account") + if (accounts.isEmpty()) { + item { + Box( + modifier = Modifier.fillParentMaxHeight(0.6f).fillMaxWidth(), + contentAlignment = Alignment.Center + ) { + Text( + "No accounts yet.\nTap 'Add New Account' to start.", + textAlign = TextAlign.Center, + color = Color.Gray + ) + } + } + } + + items(accounts) { account -> + AccountItem(account = account, onClick = { onAccountClick(account) }) + } } } } +} + +@Composable +fun AccountItem(account: Account, onClick: () -> Unit) { + ElevatedCard( + modifier = Modifier + .fillMaxWidth() + .clickable { onClick() }, + shape = MaterialTheme.shapes.large, + colors = CardDefaults.elevatedCardColors( + containerColor = MaterialTheme.colorScheme.surface + ) + ) { + Row( + modifier = Modifier.padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + // Stylized Avatar with Initial + Surface( + modifier = Modifier.size(52.dp), + shape = CircleShape, + color = MaterialTheme.colorScheme.primaryContainer + ) { + Box(contentAlignment = Alignment.Center) { + Text( + text = account.name.take(1).uppercase(), + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + } + + Spacer(modifier = Modifier.width(16.dp)) + + Column(modifier = Modifier.weight(1f)) { + Text( + text = account.name, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + Icons.Default.Home, + contentDescription = null, + modifier = Modifier.size(14.dp), + tint = MaterialTheme.colorScheme.secondary + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = account.wgName, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.secondary + ) + } + } + + // Trailing Chevron + Icon( + Icons.AutoMirrored.Filled.KeyboardArrowRight, + contentDescription = null, + tint = MaterialTheme.colorScheme.outline + ) + } + } } \ No newline at end of file diff --git a/app/src/main/java/de/miaurizius/shap_planner/ui/screens/DashboardScreen.kt b/app/src/main/java/de/miaurizius/shap_planner/ui/screens/DashboardScreen.kt index b299a91..308d689 100644 --- a/app/src/main/java/de/miaurizius/shap_planner/ui/screens/DashboardScreen.kt +++ b/app/src/main/java/de/miaurizius/shap_planner/ui/screens/DashboardScreen.kt @@ -1,7 +1,6 @@ package de.miaurizius.shap_planner.ui.screens import android.annotation.SuppressLint -import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -13,25 +12,30 @@ 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.navigationBarsPadding import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material3.Button +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Refresh import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import de.miaurizius.shap_planner.entities.Account @@ -43,115 +47,116 @@ import de.miaurizius.shap_planner.viewmodels.MainViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable fun DashboardScreen( - // Data and regarding Methods account: Account, onExpenseClick: (Expense) -> Unit, onAddExpenseClick: () -> Unit, - - // Default Methods mainViewModel: MainViewModel, onBack: () -> Unit, onDelete: () -> Unit, sessionState: SessionState, onValidate: () -> Unit, - onSessionInvalid: () -> Unit) { - + onSessionInvalid: () -> Unit +) { val expenseResource by mainViewModel.expenseResource.collectAsState() LaunchedEffect(Unit) { onValidate() } LaunchedEffect(account) { mainViewModel.loadExpenses(account, forceRefresh = false) } - when (sessionState) { - SessionState.Loading -> { - Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - CircularProgressIndicator() + + if (sessionState == SessionState.Invalid) { + LaunchedEffect(Unit) { onSessionInvalid() } + return + } + + Scaffold( + topBar = { + TopAppBar( + title = { + Column { + Text(account.wgName, style = MaterialTheme.typography.titleLarge) + Text("Household", style = MaterialTheme.typography.labelSmall) + } + }, + actions = { + IconButton(onClick = onBack) { + Icon(Icons.Default.Refresh, "Switch") + } + IconButton(onClick = onDelete) { + Icon(Icons.Default.Delete, "Delete", tint = MaterialTheme.colorScheme.error) + } + } + ) + }, + floatingActionButton = { + androidx.compose.material3.FloatingActionButton( + onClick = onAddExpenseClick, + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) { + Icon(Icons.Default.Add, "Neu") } } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .background(MaterialTheme.colorScheme.surface) + ) { + Column(modifier = Modifier.padding(16.dp)) { + Text( + text = "Hello, ${account.name}!", + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold + ) - SessionState.Valid -> { - BackHandler { - onBack() + Spacer(modifier = Modifier.height(16.dp)) + + SummaryCard(expenses = expenseResource.data ?: emptyList()) } - Scaffold(floatingActionButton = { - androidx.compose.material3.FloatingActionButton( - onClick = onAddExpenseClick, - containerColor = MaterialTheme.colorScheme.primary, - contentColor = MaterialTheme.colorScheme.onPrimary - ) { - Text("+", style = MaterialTheme.typography.headlineSmall) + + Text( + text = "Latest expenses", + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), + color = MaterialTheme.colorScheme.secondary + ) + + Box(modifier = Modifier.fillMaxSize()) { + if (expenseResource is Resource.Loading && expenseResource.data?.isEmpty() == true) { + CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) } - }) { paddingValues -> - Column( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues) - .padding(16.dp) - .statusBarsPadding() - .navigationBarsPadding() + + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) ) { - // Header - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Column { - Text( - text = "Hello, ${account.name}!", - style = MaterialTheme.typography.headlineMedium - ) - Text( - text = "Household: ${account.wgName}", - style = MaterialTheme.typography.bodyLarge, - color = Color.Gray - ) - } - Button(onClick = onBack) { - Text("Switch") - } - } - - Spacer(modifier = Modifier.height(5.dp)) - - Button(onClick = onDelete) { - Text("Delete") - } - - Spacer(modifier = Modifier.height(10.dp)) - - Text("Costs", style = MaterialTheme.typography.titleLarge) - Spacer(modifier = Modifier.height(8.dp)) - - if(expenseResource is Resource.Loading && expenseResource.data?.isEmpty() == true) { - CircularProgressIndicator(modifier = Modifier.align(Alignment.CenterHorizontally)) - } - - if(expenseResource is Resource.Error) { - Text("Error: ${expenseResource.message}", color = Color.Red) - } - - LazyColumn( - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.surfaceVariant, shape = MaterialTheme.shapes.medium), - contentPadding = PaddingValues(8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - items(expenseResource.data ?: emptyList()) { expense -> - ExpenseItem(expense = expense, onClick = { onExpenseClick(expense) }) - } + items(expenseResource.data ?: emptyList()) { expense -> + ExpenseItem(expense = expense, onClick = { onExpenseClick(expense) }) } } } } + } +} - SessionState.Invalid -> { - LaunchedEffect(Unit) { - onSessionInvalid() - } - } - - is SessionState.Error -> { - Text("Server error") +@SuppressLint("DefaultLocale") +@Composable +fun SummaryCard(expenses: List) { + val total = expenses.sumOf { it.amount } / 100.0 + androidx.compose.material3.Card( + modifier = Modifier.fillMaxWidth(), + colors = androidx.compose.material3.CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.primaryContainer + ) + ) { + Column(modifier = Modifier.padding(20.dp)) { + Text("Total expenditure", style = MaterialTheme.typography.labelMedium) + Text( + text = String.format("%.2f €", total), + style = MaterialTheme.typography.displaySmall, + fontWeight = FontWeight.Black + ) } } } @@ -159,14 +164,54 @@ fun DashboardScreen( @SuppressLint("DefaultLocale") @Composable fun ExpenseItem(expense: Expense, onClick: () -> Unit) { - Surface(modifier = Modifier - .fillMaxWidth() - .clickable{onClick()}, - shape = MaterialTheme.shapes.small, - color = MaterialTheme.colorScheme.surface) { - Row(modifier = Modifier.padding(16.dp), horizontalArrangement = Arrangement.SpaceBetween) { - Text(text = expense.title, style = MaterialTheme.typography.bodyLarge) - Text(text = String.format("%.2f€", expense.amount / 100.0), style = MaterialTheme.typography.bodyLarge, fontWeight = FontWeight.Bold) + androidx.compose.material3.ElevatedCard( + modifier = Modifier + .fillMaxWidth() + .clickable { onClick() }, + shape = MaterialTheme.shapes.medium, + colors = androidx.compose.material3.CardDefaults.elevatedCardColors( + containerColor = MaterialTheme.colorScheme.surface + ) + ) { + Row( + modifier = Modifier + .padding(16.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Surface( + shape = androidx.compose.foundation.shape.CircleShape, + color = MaterialTheme.colorScheme.primaryContainer, + modifier = Modifier.size(40.dp) + ) { + Box(contentAlignment = Alignment.Center) { + Text( + text = expense.title.take(1).uppercase(), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + } + + Spacer(modifier = Modifier.width(16.dp)) + + Column { + Text( + text = expense.title, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + } + } + + Text( + text = String.format("%.2f €", expense.amount / 100.0), + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.ExtraBold, + color = MaterialTheme.colorScheme.primary + ) } } } \ No newline at end of file diff --git a/app/src/main/java/de/miaurizius/shap_planner/ui/screens/ExpenseCreationScreen.kt b/app/src/main/java/de/miaurizius/shap_planner/ui/screens/ExpenseCreationScreen.kt index 293f97b..82c26d8 100644 --- a/app/src/main/java/de/miaurizius/shap_planner/ui/screens/ExpenseCreationScreen.kt +++ b/app/src/main/java/de/miaurizius/shap_planner/ui/screens/ExpenseCreationScreen.kt @@ -1,17 +1,28 @@ package de.miaurizius.shap_planner.ui.screens +import android.annotation.SuppressLint import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Description import androidx.compose.material3.* import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import de.miaurizius.shap_planner.entities.Account import de.miaurizius.shap_planner.viewmodels.ExpenseCreationViewModel -import java.util.UUID -@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) +@SuppressLint("DefaultLocale") +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ExpenseCreationScreen( account: Account, @@ -20,68 +31,219 @@ fun ExpenseCreationScreen( onBack: () -> Unit ) { var title by remember { mutableStateOf("") } - var amountStr by remember { mutableStateOf("") } + var description by remember { mutableStateOf("") } + var totalAmountStr by remember { mutableStateOf("") } + val attachmentUris = remember { mutableStateListOf() } + val userShares = remember { mutableStateMapOf() } val users by viewModel.users.collectAsState() - val selectedUsers = remember { mutableStateListOf() } + + // Real-time calculation logic + val totalCents = (totalAmountStr.replace(",", ".").toDoubleOrNull() ?: 0.0) * 100 + val distributedCents = userShares.values.sumOf { + (it.replace(",", ".").toDoubleOrNull() ?: 0.0) * 100 + }.toLong() + + val diff = totalCents.toLong() - distributedCents + val isAmountMatched = totalCents > 0 && diff == 0L + + // File Picker for multiple files (Images & PDFs) + val launcher = androidx.activity.compose.rememberLauncherForActivityResult( + contract = androidx.activity.result.contract.ActivityResultContracts.GetMultipleContents() + ) { uris -> + attachmentUris.addAll(uris) + } LaunchedEffect(Unit) { viewModel.loadUsers() } Scaffold( topBar = { - TopAppBar(title = { Text("New Expense") }) + TopAppBar( + title = { Text("New Expense") }, + navigationIcon = { IconButton(onClick = onBack) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back") + } } + ) } ) { padding -> - Column(modifier = Modifier.padding(padding).padding(16.dp)) { - OutlinedTextField( - value = title, - onValueChange = { title = it }, - label = { Text("What was bought?") }, - modifier = Modifier.fillMaxWidth() - ) - - Spacer(modifier = Modifier.height(8.dp)) - - OutlinedTextField( - value = amountStr, - onValueChange = { amountStr = it }, - label = { Text("Amount in €") }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), - modifier = Modifier.fillMaxWidth() - ) - - Spacer(modifier = Modifier.height(16.dp)) - Text("Who participated?", style = MaterialTheme.typography.titleMedium) - - FlowRow(modifier = Modifier.padding(vertical = 8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) { - users.forEach { user -> - FilterChip( - selected = selectedUsers.contains(user.id), - onClick = { - if (selectedUsers.contains(user.id)) selectedUsers.remove(user.id) - else selectedUsers.add(user.id) - }, - label = { Text(user.name) } - ) + LazyColumn( + modifier = Modifier.fillMaxSize().padding(padding).padding(horizontal = 16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + // Header Info + item { + Spacer(modifier = Modifier.height(8.dp)) + ElevatedCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { + OutlinedTextField( + value = title, + onValueChange = { title = it }, + label = { Text("Title *") }, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(12.dp)) + OutlinedTextField( + value = totalAmountStr, + onValueChange = { totalAmountStr = it }, + label = { Text("Total Amount (€) *") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), + modifier = Modifier.fillMaxWidth() + ) + } } } - Spacer(modifier = Modifier.weight(1f)) + // Optional Description & Multi-File Attachments + item { + Text("Additional Info", style = MaterialTheme.typography.titleMedium) + ElevatedCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { + OutlinedTextField( + value = description, + onValueChange = { description = it }, + label = { Text("Description (Optional)") }, + modifier = Modifier.fillMaxWidth(), + minLines = 2 + ) + Spacer(modifier = Modifier.height(16.dp)) - Button( - onClick = { - val cents = (amountStr.replace(",", ".").toDoubleOrNull() ?: 0.0) * 100 - viewModel.saveExpense(account, title, cents.toInt(), selectedUsers.toList()) - onSaved() - }, - modifier = Modifier.fillMaxWidth(), - enabled = title.isNotBlank() && amountStr.isNotBlank() && selectedUsers.isNotEmpty() - ) { - Text("Save") + Text("Attachments (${attachmentUris.size})", style = MaterialTheme.typography.labelLarge) + attachmentUris.forEach { uri -> + Row(verticalAlignment = Alignment.CenterVertically) { + Icon(Icons.Default.Description, "File", modifier = Modifier.size(20.dp)) + Text(" Document attached", modifier = Modifier.weight(1f), style = MaterialTheme.typography.bodySmall) + IconButton(onClick = { attachmentUris.remove(uri) }) { + Icon(Icons.Default.Delete, "Remove", tint = Color.Red) + } + } + } + + Button(onClick = { launcher.launch("*/*") }) { + Text("Select Files (Images, PDF)") + } + } + } } - TextButton(onClick = onBack, modifier = Modifier.fillMaxWidth()) { - Text("Cancel") + // Split Details & Validation Message + item { + Column { + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { + Text("Split Details *", style = MaterialTheme.typography.titleMedium) + TextButton(onClick = { + if (totalCents > 0) { + val share = String.format("%.2f", (totalCents / users.size) / 100.0) + users.forEach { userShares[it.id] = share } + } + }) { Text("Split Equally") } + } + + // VALIDATION MESSAGE + if (totalCents > 0) { + val statusText = when { + diff > 0 -> "Remaining: ${String.format("%.2f", diff / 100.0)} €" + diff < 0 -> "Over-allocated: ${String.format("%.2f", Math.abs(diff) / 100.0)} €" + else -> "Amount matched! ✓" + } + val statusColor = if (diff == 0L) Color(0xFF2E7D32) else MaterialTheme.colorScheme.error + + Surface( + color = statusColor.copy(alpha = 0.1f), + shape = MaterialTheme.shapes.small, + modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp) + ) { + Text( + text = statusText, + color = statusColor, + modifier = Modifier.padding(8.dp), + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center + ) + } + } + } + } + + items(users) { user -> + UserShareInputItem( + userName = user.name, + amount = userShares[user.id] ?: "", + onAmountChange = { userShares[user.id] = it } + ) + } + + // Save Actions + item { + Button( + onClick = { + viewModel.saveExpense( + account = account, + title = title, + description = description, + amountCents = totalCents.toInt(), + shares = userShares.mapValues { (it.value.replace(",",".").toDoubleOrNull() ?: 0.0).toInt() * 100 }.filter { it.value > 0 }, + attachments = attachmentUris.map { it.toString() } + ) + onSaved() + }, + modifier = Modifier.fillMaxWidth().height(56.dp), + enabled = title.isNotBlank() && isAmountMatched // STRICT VALIDATION + ) { + Text("Save Expense") + } + + if (totalAmountStr.isNotBlank() && !isAmountMatched) { + Text( + "Sum of shares must equal total amount to save.", + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.error, + modifier = Modifier.fillMaxWidth().padding(top = 8.dp), + textAlign = TextAlign.Center + ) + } + + TextButton(onClick = onBack, modifier = Modifier.fillMaxWidth()) { + Text("Cancel") + } + Spacer(modifier = Modifier.height(32.dp)) } } } +} + +@Composable +fun UserShareInputItem(userName: String, amount: String, onAmountChange: (String) -> Unit) { + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f)) + ) { + Row( + modifier = Modifier.padding(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Surface( + shape = androidx.compose.foundation.shape.CircleShape, + color = MaterialTheme.colorScheme.primaryContainer, + modifier = Modifier.size(32.dp) + ) { + Box(contentAlignment = Alignment.Center) { + Text(userName.take(1).uppercase(), style = MaterialTheme.typography.bodySmall) + } + } + Spacer(modifier = Modifier.width(12.dp)) + Text(userName, modifier = Modifier.weight(1f), style = MaterialTheme.typography.bodyLarge) + + OutlinedTextField( + value = amount, + onValueChange = onAmountChange, + modifier = Modifier.width(100.dp), + placeholder = { Text("0.00") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), + singleLine = true, + textStyle = MaterialTheme.typography.bodyMedium.copy(textAlign = TextAlign.End) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text("€") + } + } } \ No newline at end of file diff --git a/app/src/main/java/de/miaurizius/shap_planner/ui/screens/ExpenseDetailScreen.kt b/app/src/main/java/de/miaurizius/shap_planner/ui/screens/ExpenseDetailScreen.kt index f1265a1..12f9030 100644 --- a/app/src/main/java/de/miaurizius/shap_planner/ui/screens/ExpenseDetailScreen.kt +++ b/app/src/main/java/de/miaurizius/shap_planner/ui/screens/ExpenseDetailScreen.kt @@ -2,24 +2,26 @@ package de.miaurizius.shap_planner.ui.screens import android.annotation.SuppressLint 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.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material3.Card -import androidx.compose.material3.DividerDefaults import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable @@ -54,32 +56,81 @@ fun ExpenseDetailScreen( Scaffold( topBar = { TopAppBar( - title = { Text(expense.title) }, - navigationIcon = { IconButton(onClick = onBack) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back") - } } + title = { Text("Expense Details") }, + navigationIcon = { + IconButton(onClick = onBack) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") + } + } ) } ) { padding -> - Column(modifier = Modifier.padding(padding).padding(16.dp)) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .padding(16.dp) + ) { + // Hero Section: Amount & Title + Column( + modifier = Modifier.fillMaxWidth().padding(bottom = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "Total Amount", + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.secondary + ) + Text( + text = String.format("%.2f €", expense.amount / 100.0), + style = MaterialTheme.typography.displayMedium, + fontWeight = FontWeight.Black, + color = MaterialTheme.colorScheme.primary + ) + Text( + text = expense.title, + style = MaterialTheme.typography.headlineSmall, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center + ) + } + + // Description Card (only shown if not empty) + if (expense.description.isNotBlank()) { + androidx.compose.material3.ElevatedCard( + modifier = Modifier.fillMaxWidth().padding(bottom = 24.dp), + colors = androidx.compose.material3.CardDefaults.elevatedCardColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f) + ) + ) { + Column(modifier = Modifier.padding(16.dp)) { + Text( + text = "Description", + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.primary + ) + Spacer(modifier = Modifier.height(4.dp)) + Text(text = expense.description, style = MaterialTheme.typography.bodyLarge) + } + } + } + Text( - text = String.format("%.2f€", expense.amount / 100.0), - style = MaterialTheme.typography.displayMedium, + text = "Cost Distribution", + style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.primary + modifier = Modifier.padding(bottom = 12.dp) ) - Text(expense.description, style = MaterialTheme.typography.bodyLarge) - Spacer(modifier = Modifier.height(24.dp)) - HorizontalDivider(Modifier, DividerDefaults.Thickness, DividerDefaults.color) - Spacer(modifier = Modifier.height(16.dp)) - - Text("Cost Allocation", style = MaterialTheme.typography.titleLarge) - Spacer(modifier = Modifier.height(8.dp)) - - LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) { + LazyColumn( + verticalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier.fillMaxSize() + ) { items(shares) { item -> - ShareItem(item.user?.name ?: "Unknown User", item.share.share_cents) + ShareItem( + name = item.user?.name ?: "Unknown User", + amountCents = item.share.share_cents + ) } } } @@ -89,20 +140,49 @@ fun ExpenseDetailScreen( @SuppressLint("DefaultLocale") @Composable fun ShareItem(name: String, amountCents: Int) { - Card(modifier = Modifier - .fillMaxWidth() - .padding(vertical = 4.dp) + androidx.compose.material3.OutlinedCard( + modifier = Modifier.fillMaxWidth(), + shape = MaterialTheme.shapes.medium, + colors = androidx.compose.material3.CardDefaults.outlinedCardColors( + containerColor = MaterialTheme.colorScheme.surface + ) ) { Row( - modifier = Modifier.fillMaxWidth().padding(16.dp), + modifier = Modifier + .fillMaxWidth() + .padding(12.dp), verticalAlignment = Alignment.CenterVertically ) { - Text(name, fontWeight = FontWeight.Medium, modifier = Modifier.weight(1f)) + // User Avatar Circle + Surface( + shape = androidx.compose.foundation.shape.CircleShape, + color = MaterialTheme.colorScheme.secondaryContainer, + modifier = Modifier.size(36.dp) + ) { + Box(contentAlignment = Alignment.Center) { + Text( + text = name.take(1).uppercase(), + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSecondaryContainer + ) + } + } + + Spacer(modifier = Modifier.width(12.dp)) + Text( - text = String.format("%.2f€", amountCents / 100.0), - color = if (amountCents > 0) Color(0xFF4CAF50) else Color.Gray, + text = name, + style = MaterialTheme.typography.bodyLarge, + fontWeight = FontWeight.Medium, + modifier = Modifier.weight(1f) + ) + + Text( + text = String.format("%.2f €", amountCents / 100.0), + style = MaterialTheme.typography.bodyLarge, fontWeight = FontWeight.Bold, - style = MaterialTheme.typography.bodyLarge + color = if (amountCents > 0) Color(0xFF2E7D32) else Color.Gray ) } } diff --git a/app/src/main/java/de/miaurizius/shap_planner/ui/screens/LoginScreen.kt b/app/src/main/java/de/miaurizius/shap_planner/ui/screens/LoginScreen.kt index 2d73eab..b6a41a4 100644 --- a/app/src/main/java/de/miaurizius/shap_planner/ui/screens/LoginScreen.kt +++ b/app/src/main/java/de/miaurizius/shap_planner/ui/screens/LoginScreen.kt @@ -3,70 +3,174 @@ package de.miaurizius.shap_planner.ui.screens import androidx.activity.compose.BackHandler 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.navigationBarsPadding import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Cloud +import androidx.compose.material.icons.filled.Lock +import androidx.compose.material.icons.filled.Person +import androidx.compose.material.icons.filled.Visibility +import androidx.compose.material.icons.filled.VisibilityOff +import androidx.compose.material.icons.filled.VpnKey import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.TextField +import androidx.compose.material3.TextButton +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @OptIn(ExperimentalMaterial3Api::class) @Composable -fun LoginScreen(onLogin: (String, String, String) -> Unit, onBack: (() -> Unit)? = null) { - +fun LoginScreen( + onLogin: (String, String, String) -> Unit, + onBack: (() -> Unit)? = null +) { if (onBack != null) { - BackHandler { - onBack() - } + BackHandler { onBack() } } var serverUrl by remember { mutableStateOf("") } var username by remember { mutableStateOf("") } var password by remember { mutableStateOf("") } + var passwordVisible by remember { mutableStateOf(false) } - Column(modifier = Modifier.padding(16.dp).statusBarsPadding().navigationBarsPadding()) { - Text("Bitte anmelden") - Spacer(modifier = Modifier.height(8.dp)) + Scaffold( + topBar = { + TopAppBar( + title = { Text("Add Account", fontWeight = FontWeight.Bold) }, + navigationIcon = { + if (onBack != null) { + IconButton(onClick = onBack) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") + } + } + } + ) + } + ) { padding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .padding(24.dp) + .verticalScroll(rememberScrollState()), // Scrollbar, falls die Tastatur den Platz wegnimmt + horizontalAlignment = Alignment.CenterHorizontally + ) { + // App Icon or Welcome Text + Icon( + imageVector = Icons.Default.Lock, + contentDescription = null, + modifier = Modifier.size(64.dp), + tint = MaterialTheme.colorScheme.primary + ) - //Home-Server - TextField( - value = serverUrl, - onValueChange = { serverUrl = it }, - label = { Text("Server-Domain") } - ) - Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "Connect to Server", + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(top = 16.dp) + ) - //Username - TextField( - value = username, - onValueChange = { username = it }, - label = { Text("Nutzername") } - ) - Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "Enter your credentials to link your account", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.secondary, + textAlign = TextAlign.Center, + modifier = Modifier.padding(bottom = 32.dp) + ) - //Password - TextField( - value = password, - onValueChange = { password = it }, - label = { Text("Passwort") } - ) - Spacer(modifier = Modifier.height(8.dp)) + // Server URL Field + OutlinedTextField( + value = serverUrl, + onValueChange = { serverUrl = it }, + label = { Text("Server URL") }, + placeholder = { Text("your-server.com") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true, + leadingIcon = { Icon(Icons.Default.Cloud, contentDescription = null) }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri, imeAction = ImeAction.Next) + ) - Button(onClick = { if(serverUrl.isNotEmpty() && username.isNotEmpty() && password.isNotEmpty()) onLogin( - serverUrl, - username, - password - ) }) { - Text("Login") + Spacer(modifier = Modifier.height(16.dp)) + + // Username Field + OutlinedTextField( + value = username, + onValueChange = { username = it }, + label = { Text("Username") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true, + leadingIcon = { Icon(Icons.Default.Person, contentDescription = null) }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next) + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // Password Field + OutlinedTextField( + value = password, + onValueChange = { password = it }, + label = { Text("Password") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true, + leadingIcon = { Icon(Icons.Default.VpnKey, contentDescription = null) }, + visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Done + ), + trailingIcon = { + val image = if (passwordVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff + IconButton(onClick = { passwordVisible = !passwordVisible }) { + Icon(imageVector = image, contentDescription = "Toggle password visibility") + } + } + ) + + Spacer(modifier = Modifier.height(32.dp)) + + // Login Button + Button( + onClick = { onLogin(serverUrl, username, password) }, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + enabled = serverUrl.isNotBlank() && username.isNotBlank() && password.isNotBlank(), + shape = MaterialTheme.shapes.medium + ) { + Text("Connect Account", style = MaterialTheme.typography.titleMedium) + } + + if (onBack != null) { + TextButton(onClick = onBack, modifier = Modifier.padding(top = 8.dp)) { + Text("Cancel", color = MaterialTheme.colorScheme.secondary) + } + } } } } \ No newline at end of file diff --git a/app/src/main/java/de/miaurizius/shap_planner/viewmodels/ExpenseCreationViewModel.kt b/app/src/main/java/de/miaurizius/shap_planner/viewmodels/ExpenseCreationViewModel.kt index f8dafe1..66558af 100644 --- a/app/src/main/java/de/miaurizius/shap_planner/viewmodels/ExpenseCreationViewModel.kt +++ b/app/src/main/java/de/miaurizius/shap_planner/viewmodels/ExpenseCreationViewModel.kt @@ -30,35 +30,37 @@ class ExpenseCreationViewModel( } } - fun saveExpense(account: Account, title: String, amountCents: Int, selectedUserIds: List) { - viewModelScope.launch { - val expenseId = UUID.randomUUID() //TODO: Backend has to generate UUID - val newExpense = Expense( - id = expenseId, - payer_id = account.id, - amount = amountCents, - title = title, - description = "", - attachments = null, - created_at = (System.currentTimeMillis() / 1000).toInt(), - last_updated_at = 0 - ) - - expenseDao.insertExpense(newExpense) - - val shareAmount = amountCents / selectedUserIds.size - selectedUserIds.forEach { userId -> - shareDao.insertShare( - ExpenseShare( - UUID.randomUUID(), //TODO: Backend has to generate UUID - expenseId, - userId, - shareAmount - ) - ) - } - - // API Calls - } + fun saveExpense( + account: Account, + title: String, + description: String, + amountCents: Int, + shares: Map, + attachments: List + ) { +// viewModelScope.launch { +// val expenseId = UUID.randomUUID() +// val newExpense = Expense( +// id = expenseId, +// payer_id = account.id, +// amount = amountCents, +// title = title, +// description = description, +// attachments = if (attachments.isEmpty()) null else attachments, +// created_at = (System.currentTimeMillis() / 1000).toInt(), +// last_updated_at = 0 +// ) +// +// expenseDao.insertExpense(newExpense) +// +// shares.forEach { (userId, shareCents) -> +// shareDao.insertShare( +// ExpenseShare(UUID.randomUUID(), expenseId, userId, shareCents) +// ) +// } +// +// // API POST Request +// +// } } } \ No newline at end of file diff --git a/app/src/main/java/de/miaurizius/shap_planner/viewmodels/MainViewModel.kt b/app/src/main/java/de/miaurizius/shap_planner/viewmodels/MainViewModel.kt index 60058e8..4f5be4d 100644 --- a/app/src/main/java/de/miaurizius/shap_planner/viewmodels/MainViewModel.kt +++ b/app/src/main/java/de/miaurizius/shap_planner/viewmodels/MainViewModel.kt @@ -82,10 +82,6 @@ class MainViewModel( sessionState = SessionState.Valid - // Fetch data -// loadExpenses(account) - println("All data fetched") - return@launch } else { sessionState = SessionState.Invalid