From 7e00b14d272fcd444afbc676c47fde1f1aafb8a2 Mon Sep 17 00:00:00 2001 From: soraefir Date: Mon, 29 Jun 2026 21:27:27 +0200 Subject: [PATCH] Fix keyboard padding and better currency management --- .../android/bill_edit/EditBillScreen.kt | 1 + .../currencies/ManageCurrenciesActivity.kt | 44 ++- .../currencies/ManageCurrenciesScreen.kt | 298 ++++++++++++------ .../currencies/ManageCurrenciesViewModel.kt | 2 + .../project/create/NewProjectScreen.kt | 1 + .../android/project/edit/EditProjectScreen.kt | 5 + .../net/helcel/cowspent/theme/ThemeUtils.kt | 1 + 7 files changed, 248 insertions(+), 104 deletions(-) diff --git a/app/src/main/java/net/helcel/cowspent/android/bill_edit/EditBillScreen.kt b/app/src/main/java/net/helcel/cowspent/android/bill_edit/EditBillScreen.kt index 3cf7784..1aeeb30 100644 --- a/app/src/main/java/net/helcel/cowspent/android/bill_edit/EditBillScreen.kt +++ b/app/src/main/java/net/helcel/cowspent/android/bill_edit/EditBillScreen.kt @@ -117,6 +117,7 @@ fun EditBillScreen( Column( modifier = Modifier .padding(padding) + .imePadding() .padding(16.dp) .fillMaxSize() .verticalScroll(scrollState) diff --git a/app/src/main/java/net/helcel/cowspent/android/currencies/ManageCurrenciesActivity.kt b/app/src/main/java/net/helcel/cowspent/android/currencies/ManageCurrenciesActivity.kt index bca038e..41dda19 100644 --- a/app/src/main/java/net/helcel/cowspent/android/currencies/ManageCurrenciesActivity.kt +++ b/app/src/main/java/net/helcel/cowspent/android/currencies/ManageCurrenciesActivity.kt @@ -67,8 +67,10 @@ class ManageCurrenciesActivity : AppCompatActivity() { viewModel = viewModel, onBack = { finish() }, onSaveMain = { saveMainCurrency() }, - onAdd = { addCurrency() }, - onDelete = { deleteCurrency(it) } + onAdd = { addOrUpdateCurrency() }, + onDelete = { deleteCurrency(it) }, + onEdit = { startEditing(it) }, + onCancelEdit = { cancelEditing() } ) } } @@ -105,22 +107,44 @@ class ManageCurrenciesActivity : AppCompatActivity() { } } - private fun addCurrency() { + private fun addOrUpdateCurrency() { val exchangeRate = try { viewModel.newCurrencyRate.toDouble() } catch (_: Exception) { 0.0 } - val newCurrency = DBCurrency( - 0, 0, selectedProjectID, - viewModel.newCurrencyName, exchangeRate, DBBill.STATE_ADDED - ) + val currencyName = viewModel.newCurrencyName + val editingId = viewModel.editingCurrencyId + lifecycleScope.launch { withContext(Dispatchers.IO) { - db!!.addCurrencyAndSync(newCurrency) + if (editingId != null) { + db!!.updateCurrency(editingId, currencyName, exchangeRate) + val currency = db!!.getCurrency(editingId) + if (currency != null) { + db!!.setCurrencyStateSync(editingId, DBBill.STATE_EDITED) + } + } else { + val newCurrency = DBCurrency( + 0, 0, selectedProjectID, + currencyName, exchangeRate, DBBill.STATE_ADDED + ) + db!!.addCurrencyAndSync(newCurrency) + } } - viewModel.newCurrencyName = "" - viewModel.newCurrencyRate = "" + cancelEditing() updateCurrenciesList() } } + private fun startEditing(currency: DBCurrency) { + viewModel.editingCurrencyId = currency.id + viewModel.newCurrencyName = currency.name ?: "" + viewModel.newCurrencyRate = currency.exchangeRate.toString() + } + + private fun cancelEditing() { + viewModel.editingCurrencyId = null + viewModel.newCurrencyName = "" + viewModel.newCurrencyRate = "" + } + private fun deleteCurrency(currency: DBCurrency) { lifecycleScope.launch { withContext(Dispatchers.IO) { diff --git a/app/src/main/java/net/helcel/cowspent/android/currencies/ManageCurrenciesScreen.kt b/app/src/main/java/net/helcel/cowspent/android/currencies/ManageCurrenciesScreen.kt index ef2261c..15d428d 100644 --- a/app/src/main/java/net/helcel/cowspent/android/currencies/ManageCurrenciesScreen.kt +++ b/app/src/main/java/net/helcel/cowspent/android/currencies/ManageCurrenciesScreen.kt @@ -1,6 +1,9 @@ package net.helcel.cowspent.android.currencies import android.annotation.SuppressLint +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -8,16 +11,25 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Done import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import kotlinx.coroutines.delay import net.helcel.cowspent.R import net.helcel.cowspent.android.helper.AlertDialog +import net.helcel.cowspent.android.helper.TextIcon +import net.helcel.cowspent.android.helper.TextIconDisplay import net.helcel.cowspent.android.helper.formatAmount import net.helcel.cowspent.model.DBCurrency @@ -27,7 +39,9 @@ fun ManageCurrenciesScreen( onBack: () -> Unit, onSaveMain: () -> Unit, onAdd: () -> Unit, - onDelete: (DBCurrency) -> Unit + onDelete: (DBCurrency) -> Unit, + onEdit: (DBCurrency) -> Unit, + onCancelEdit: () -> Unit ) { val dialogState = viewModel.dialogState if (dialogState != null) { @@ -76,78 +90,152 @@ fun ManageCurrenciesScreen( Column( modifier = Modifier .padding(padding) - .padding(16.dp) + .imePadding() .fillMaxSize() + .background(MaterialTheme.colors.onSurface.copy(alpha = 0.02f)) ) { - Text(stringResource(R.string.main_currency), style = MaterialTheme.typography.h6) - Row(verticalAlignment = Alignment.CenterVertically) { - OutlinedTextField( - value = viewModel.mainCurrencyName, - onValueChange = { viewModel.mainCurrencyName = it }, - label = { Text(stringResource(R.string.currency_edit_name)) }, - modifier = Modifier.weight(1f) - ) - Spacer(modifier = Modifier.width(8.dp)) - Button(onClick = onSaveMain, enabled = viewModel.mainCurrencyName.isNotEmpty()) { - Text(stringResource(R.string.save_or_discard_bill_dialog_save)) + LaunchedEffect(viewModel.mainCurrencyName) { + if (viewModel.mainCurrencyName.isNotEmpty()) { + delay(500) + onSaveMain() } } - Spacer(modifier = Modifier.height(24.dp)) - - Text(stringResource(R.string.add_currency_title), style = MaterialTheme.typography.h6) - - // Visual relationship indicator - Surface( - color = MaterialTheme.colors.primary.copy(alpha = 0.05f), - shape = RoundedCornerShape(8.dp), - modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp) - ) { - Row( - modifier = Modifier.padding(12.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center + Column(modifier = Modifier.padding(16.dp)) { + // Section 1: Main Currency + Text( + text = stringResource(R.string.main_currency).uppercase(), + style = MaterialTheme.typography.overline, + color = MaterialTheme.colors.primary, + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.height(8.dp)) + Card( + shape = RoundedCornerShape(12.dp), + elevation = 2.dp, + modifier = Modifier.fillMaxWidth() ) { - Text("1 ", style = MaterialTheme.typography.body1) - Text(viewModel.mainCurrencyName.ifEmpty { "$" }, fontWeight = FontWeight.Bold) - Text(" = ", style = MaterialTheme.typography.h6) - Text(viewModel.newCurrencyRate.ifEmpty { "0.0" }, fontWeight = FontWeight.Bold, color = MaterialTheme.colors.primary) - Text(" ") - Text(viewModel.newCurrencyName.ifEmpty { "Currency" }, fontWeight = FontWeight.Bold) + OutlinedTextField( + value = viewModel.mainCurrencyName, + onValueChange = { viewModel.mainCurrencyName = it }, + modifier = Modifier.fillMaxWidth(), + placeholder = { Text("e.g. EUR", fontSize = 14.sp) }, + colors = TextFieldDefaults.outlinedTextFieldColors( + focusedBorderColor = Color.Transparent, + unfocusedBorderColor = Color.Transparent + ) + ) } + + Spacer(modifier = Modifier.height(24.dp)) + + // Section 2: Exchange Rates + val isEditing = viewModel.editingCurrencyId != null + Text( + text = (if (isEditing) "Edit exchange rate" else "Add exchange rate").uppercase(), + style = MaterialTheme.typography.overline, + color = if (isEditing) MaterialTheme.colors.secondary else MaterialTheme.colors.primary, + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.height(8.dp)) + + // Integrated Add/Edit Ribbon + Surface( + color = if (isEditing) MaterialTheme.colors.secondary.copy(alpha = 0.08f) else MaterialTheme.colors.surface, + shape = RoundedCornerShape(12.dp), + modifier = Modifier.fillMaxWidth(), + elevation = if (isEditing) 4.dp else 2.dp, + border = if (isEditing) BorderStroke(2.dp, MaterialTheme.colors.secondary) else null + ) { + Row( + modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text("1 ", style = MaterialTheme.typography.body2, color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)) + Text(viewModel.mainCurrencyName.ifEmpty { "$" }, fontWeight = FontWeight.Bold) + Text( + text = " = ", + style = MaterialTheme.typography.h6, + color = if (isEditing) MaterialTheme.colors.secondary else MaterialTheme.colors.primary, + modifier = Modifier.padding(horizontal = 4.dp) + ) + + OutlinedTextField( + value = viewModel.newCurrencyRate, + onValueChange = { viewModel.newCurrencyRate = it }, + modifier = Modifier.weight(1.2f).height(52.dp), + placeholder = { Text("Rate", fontSize = 12.sp) }, + singleLine = true, + textStyle = LocalTextStyle.current.copy(fontWeight = FontWeight.ExtraBold, fontSize = 14.sp), + shape = RoundedCornerShape(8.dp), + colors = TextFieldDefaults.outlinedTextFieldColors( + focusedBorderColor = if (isEditing) MaterialTheme.colors.secondary else MaterialTheme.colors.primary, + unfocusedBorderColor = MaterialTheme.colors.onSurface.copy(alpha = 0.12f), + backgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.02f) + ) + ) + Spacer(modifier = Modifier.width(8.dp)) + OutlinedTextField( + value = viewModel.newCurrencyName, + onValueChange = { viewModel.newCurrencyName = it }, + modifier = Modifier.weight(1f).height(52.dp), + placeholder = { Text("Code", fontSize = 12.sp) }, + singleLine = true, + textStyle = LocalTextStyle.current.copy(fontWeight = FontWeight.Bold, fontSize = 14.sp), + shape = RoundedCornerShape(8.dp), + colors = TextFieldDefaults.outlinedTextFieldColors( + focusedBorderColor = if (isEditing) MaterialTheme.colors.secondary else MaterialTheme.colors.primary, + unfocusedBorderColor = MaterialTheme.colors.onSurface.copy(alpha = 0.12f), + backgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.02f) + ) + ) + + Row(modifier = Modifier.padding(start = 4.dp)) { + IconButton( + onClick = onAdd, + enabled = viewModel.isAddEnabled() + ) { + Icon( + imageVector = if (isEditing) Icons.Default.Done else Icons.Default.Add, + contentDescription = null, + tint = if (viewModel.isAddEnabled()) { + if (isEditing) MaterialTheme.colors.secondary else MaterialTheme.colors.primary + } else MaterialTheme.colors.onSurface.copy(alpha = 0.2f) + ) + } + + if (isEditing) { + IconButton(onClick = onCancelEdit) { + Icon(Icons.Default.Close, contentDescription = null, tint = MaterialTheme.colors.error) + } + } + } + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + Text( + text = "SAVED RATES", + style = MaterialTheme.typography.overline, + color = MaterialTheme.colors.onSurface.copy(alpha = 0.4f), + fontWeight = FontWeight.Bold + ) } - Row(verticalAlignment = Alignment.CenterVertically) { - OutlinedTextField( - value = viewModel.newCurrencyName, - onValueChange = { viewModel.newCurrencyName = it }, - label = { Text(stringResource(R.string.currency_edit_name)) }, - modifier = Modifier.weight(1f), - placeholder = { Text("e.g. USD") } - ) - Spacer(modifier = Modifier.width(8.dp)) - OutlinedTextField( - value = viewModel.newCurrencyRate, - onValueChange = { viewModel.newCurrencyRate = it }, - label = { Text(stringResource(R.string.currency_rate)) }, - modifier = Modifier.weight(1f), - placeholder = { Text("1.0") } - ) - } - Spacer(modifier = Modifier.height(8.dp)) - Button( - onClick = onAdd, - modifier = Modifier.fillMaxWidth(), - enabled = viewModel.isAddEnabled() + LazyColumn( + modifier = Modifier.weight(1f).fillMaxWidth(), + contentPadding = PaddingValues(16.dp, 0.dp, 16.dp, 16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) ) { - Text(stringResource(R.string.simple_add)) - } - - Spacer(modifier = Modifier.height(24.dp)) - - LazyColumn(modifier = Modifier.weight(1f)) { items(viewModel.currencies) { currency -> - CurrencyRow(currency, viewModel.mainCurrencyName, onDelete = { onDelete(currency) }) + CurrencyRow( + currency = currency, + mainCurrencyName = viewModel.mainCurrencyName, + isEditing = viewModel.editingCurrencyId == currency.id, + onEdit = { onEdit(currency) }, + onDelete = { onDelete(currency) } + ) } } } @@ -155,52 +243,70 @@ fun ManageCurrenciesScreen( } @Composable -fun CurrencyRow(currency: DBCurrency, mainCurrencyName: String, onDelete: () -> Unit) { - Row( - modifier = Modifier.fillMaxWidth().padding(vertical = 12.dp), - verticalAlignment = Alignment.CenterVertically +fun CurrencyRow( + currency: DBCurrency, + mainCurrencyName: String, + isEditing: Boolean, + onEdit: () -> Unit, + onDelete: () -> Unit +) { + Card( + shape = RoundedCornerShape(12.dp), + elevation = if (isEditing) 4.dp else 1.dp, + border = if (isEditing) BorderStroke(1.dp, MaterialTheme.colors.secondary.copy(alpha = 0.5f)) else null, + modifier = Modifier.fillMaxWidth() ) { - // Table-like layout Row( - modifier = Modifier.weight(1f), + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onEdit) + .padding(16.dp, 12.dp), verticalAlignment = Alignment.CenterVertically ) { - Text( - text = "1 ", - style = MaterialTheme.typography.body1, - color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f) - ) - Text( - text = mainCurrencyName.ifEmpty { "$" }, - style = MaterialTheme.typography.body1, - fontWeight = FontWeight.Bold - ) - Text( - text = " = ", - style = MaterialTheme.typography.h6, - color = MaterialTheme.colors.primary, - modifier = Modifier.padding(horizontal = 8.dp) - ) - Column { + Row( + modifier = Modifier.weight(1f), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "1 ", + style = MaterialTheme.typography.body2, + color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f) + ) + Text( + text = mainCurrencyName.ifEmpty { "$" }, + style = MaterialTheme.typography.body1, + fontWeight = FontWeight.Bold + ) + Text( + text = " = ", + style = MaterialTheme.typography.h6, + color = if (isEditing) MaterialTheme.colors.secondary else MaterialTheme.colors.primary, + modifier = Modifier.padding(horizontal = 8.dp) + ) Text( text = formatAmount(currency.exchangeRate), style = MaterialTheme.typography.body1, - fontWeight = FontWeight.ExtraBold + fontWeight = FontWeight.ExtraBold, + color = if (isEditing) MaterialTheme.colors.secondary else MaterialTheme.colors.onSurface ) Text( - text = currency.name ?: "", - style = MaterialTheme.typography.caption, + text = " ${currency.name ?: ""}", + style = MaterialTheme.typography.body1, fontWeight = FontWeight.Bold, color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f) ) } - } - - IconButton(onClick = onDelete) { - Icon(Icons.Default.Delete, contentDescription = null, tint = MaterialTheme.colors.error) + + IconButton(onClick = onDelete) { + Icon( + imageVector = Icons.Default.Delete, + contentDescription = null, + tint = MaterialTheme.colors.error.copy(alpha = 0.7f), + modifier = Modifier.size(20.dp) + ) + } } } - Divider() } @Preview(showBackground = true) @@ -210,6 +316,8 @@ fun CurrencyRowPreview() { CurrencyRow( currency = DBCurrency(1, 0, 0, "USD", 1.0, 0), mainCurrencyName = "EUR", + isEditing = false, + onEdit = {}, onDelete = {} ) } @@ -231,7 +339,9 @@ fun ManageCurrenciesScreenPreview() { onBack = {}, onSaveMain = {}, onAdd = {}, - onDelete = {} + onDelete = {}, + onEdit = {}, + onCancelEdit = {} ) } } diff --git a/app/src/main/java/net/helcel/cowspent/android/currencies/ManageCurrenciesViewModel.kt b/app/src/main/java/net/helcel/cowspent/android/currencies/ManageCurrenciesViewModel.kt index f4e0002..9d80694 100644 --- a/app/src/main/java/net/helcel/cowspent/android/currencies/ManageCurrenciesViewModel.kt +++ b/app/src/main/java/net/helcel/cowspent/android/currencies/ManageCurrenciesViewModel.kt @@ -13,6 +13,8 @@ class ManageCurrenciesViewModel : ViewModel() { var newCurrencyName by mutableStateOf("") var newCurrencyRate by mutableStateOf("") + var editingCurrencyId by mutableStateOf(null) + var currencies by mutableStateOf>(emptyList()) var dialogState by mutableStateOf(null) diff --git a/app/src/main/java/net/helcel/cowspent/android/project/create/NewProjectScreen.kt b/app/src/main/java/net/helcel/cowspent/android/project/create/NewProjectScreen.kt index fa12f85..ab0efce 100644 --- a/app/src/main/java/net/helcel/cowspent/android/project/create/NewProjectScreen.kt +++ b/app/src/main/java/net/helcel/cowspent/android/project/create/NewProjectScreen.kt @@ -63,6 +63,7 @@ fun NewProjectScreen( Column( modifier = Modifier .padding(padding) + .imePadding() .padding(16.dp) .verticalScroll(rememberScrollState()) .fillMaxSize() diff --git a/app/src/main/java/net/helcel/cowspent/android/project/edit/EditProjectScreen.kt b/app/src/main/java/net/helcel/cowspent/android/project/edit/EditProjectScreen.kt index 350568b..2b9ad31 100644 --- a/app/src/main/java/net/helcel/cowspent/android/project/edit/EditProjectScreen.kt +++ b/app/src/main/java/net/helcel/cowspent/android/project/edit/EditProjectScreen.kt @@ -2,6 +2,8 @@ package net.helcel.cowspent.android.project.edit import android.annotation.SuppressLint import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack @@ -80,11 +82,14 @@ fun EditProjectScreen( } } ) { padding -> + val scrollState = rememberScrollState() Column( modifier = Modifier .padding(padding) + .imePadding() .padding(16.dp) .fillMaxSize() + .verticalScroll(scrollState) ) { OutlinedTextField( value = viewModel.name, diff --git a/app/src/main/java/net/helcel/cowspent/theme/ThemeUtils.kt b/app/src/main/java/net/helcel/cowspent/theme/ThemeUtils.kt index 2b371fa..cb42bc9 100644 --- a/app/src/main/java/net/helcel/cowspent/theme/ThemeUtils.kt +++ b/app/src/main/java/net/helcel/cowspent/theme/ThemeUtils.kt @@ -111,6 +111,7 @@ object ThemeUtils { modifier = Modifier .weight(1f) .navigationBarsPadding() + .imePadding() ) { content() }