Fix currency and added user weighting

This commit is contained in:
2026-09-03 22:00:26 +02:00
parent 488aa9bba6
commit 492230faa2
5 changed files with 123 additions and 23 deletions
@@ -333,7 +333,7 @@ class EditBillActivity : AppCompatActivity() {
viewModel.owersSelection[id] == true && viewModel.parseAmountFromUi(amountStr) > 0 viewModel.owersSelection[id] == true && viewModel.parseAmountFromUi(amountStr) > 0
}.mapValues { }.mapValues {
val uiAmount = viewModel.parseAmountFromUi(it.value) val uiAmount = viewModel.parseAmountFromUi(it.value)
SupportUtil.round2(uiAmount / viewModel.selectedCurrencyRate) SupportUtil.round2(uiAmount * viewModel.selectedCurrencyRate)
} }
} else { // PERCENT } else { // PERCENT
val totalAmount = viewModel.amountAsDouble val totalAmount = viewModel.amountAsDouble
@@ -342,7 +342,7 @@ class EditBillActivity : AppCompatActivity() {
}.mapValues { }.mapValues {
val percent = viewModel.parseAmountFromUi(it.value) val percent = viewModel.parseAmountFromUi(it.value)
val uiAmount = SupportUtil.round2(totalAmount * percent / 100.0) val uiAmount = SupportUtil.round2(totalAmount * percent / 100.0)
SupportUtil.round2(uiAmount / viewModel.selectedCurrencyRate) SupportUtil.round2(uiAmount * viewModel.selectedCurrencyRate)
} }
} }
@@ -450,7 +450,10 @@ fun OwerSelectionSection(
size = 32.dp size = 32.dp
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Text(member.name, modifier = Modifier.weight(1f)) val weightSuffix = if (viewModel.hasDifferentWeights && member.weight != 1.0) {
" (x${member.weight.toString().removeSuffix(".0")})"
} else ""
Text("${member.name}$weightSuffix", modifier = Modifier.weight(1f))
if (isSelected || viewModel.splitMode != SplitMode.EVEN) { if (isSelected || viewModel.splitMode != SplitMode.EVEN) {
val focusManager = LocalFocusManager.current val focusManager = LocalFocusManager.current
@@ -671,19 +674,115 @@ fun BillAdditionalDetailsSection(
} }
@SuppressLint("ViewModelConstructorInComposable") @SuppressLint("ViewModelConstructorInComposable")
@Preview(showBackground = true) @Preview(showBackground = true, name = "Standard Split")
@Composable @Composable
fun EditBillScreenPreview() { fun EditBillScreenPreview() {
MaterialTheme { MaterialTheme {
EditBillScreen( EditBillScreen(
viewModel = EditBillViewModel().apply { viewModel = EditBillViewModel().apply {
what = "Pizza" what = "Pizza"
amount = "12.50" amount = "12.00"
mainCurrencyName = "EUR" mainCurrencyName = "EUR"
members = listOf( members = listOf(
DBMember(1, 0, 0, "Alice", true, 1.0, 0, null, null, null, null, null), DBMember(1, 0, 0, "Alice", true, 1.0, 0, null, null, null, null, null),
DBMember(2, 0, 0, "Bob", true, 1.0, 0, null, null, null, null, null) DBMember(2, 0, 0, "Bob", true, 1.0, 0, null, null, null, null, null)
) )
owersSelection[1] = true
owersSelection[2] = true
updateSplits()
},
categories = emptyList(),
paymentModes = emptyList(),
onSave = {},
onBack = {},
onDateClick = {},
onTimeClick = {},
onScan = {}
)
}
}
@SuppressLint("ViewModelConstructorInComposable")
@Preview(showBackground = true, name = "Weighted Split")
@Composable
fun EditBillScreenWeightedPreview() {
MaterialTheme {
EditBillScreen(
viewModel = EditBillViewModel().apply {
what = "Weighted Pizza"
amount = "60.00"
mainCurrencyName = "EUR"
members = listOf(
DBMember(1, 0, 0, "Alice", true, 2.0, 0, null, null, null, null, null),
DBMember(2, 0, 0, "Bob", true, 1.0, 0, null, null, null, null, null),
DBMember(3, 0, 0, "Charlie", true, 1.0, 0, null, null, null, null, null)
)
owersSelection[1] = true
owersSelection[2] = true
owersSelection[3] = true
updateSplits()
},
categories = emptyList(),
paymentModes = emptyList(),
onSave = {},
onBack = {},
onDateClick = {},
onTimeClick = {},
onScan = {}
)
}
}
@SuppressLint("ViewModelConstructorInComposable")
@Preview(showBackground = true, name = "Custom Split (#)")
@Composable
fun EditBillScreenCustomPreview() {
MaterialTheme {
EditBillScreen(
viewModel = EditBillViewModel().apply {
what = "Custom Split Pizza"
amount = "60.00"
mainCurrencyName = "EUR"
members = listOf(
DBMember(1, 0, 0, "Alice", true, 1.0, 0, null, null, null, null, null),
DBMember(2, 0, 0, "Bob", true, 1.0, 0, null, null, null, null, null)
)
splitMode = SplitMode.CUSTOM
owersSelection[1] = true
owersSelection[2] = true
owersCustomSplit[1] = "40.00"
owersCustomSplit[2] = "20.00"
},
categories = emptyList(),
paymentModes = emptyList(),
onSave = {},
onBack = {},
onDateClick = {},
onTimeClick = {},
onScan = {}
)
}
}
@SuppressLint("ViewModelConstructorInComposable")
@Preview(showBackground = true, name = "Percent Split (%)")
@Composable
fun EditBillScreenPercentPreview() {
MaterialTheme {
EditBillScreen(
viewModel = EditBillViewModel().apply {
what = "Percent Split Pizza"
amount = "100.00"
mainCurrencyName = "EUR"
members = listOf(
DBMember(1, 0, 0, "Alice", true, 1.0, 0, null, null, null, null, null),
DBMember(2, 0, 0, "Bob", true, 1.0, 0, null, null, null, null, null)
)
splitMode = SplitMode.PERCENT
owersSelection[1] = true
owersSelection[2] = true
owersPercentSplit[1] = "70"
owersPercentSplit[2] = "30"
}, },
categories = emptyList(), categories = emptyList(),
paymentModes = emptyList(), paymentModes = emptyList(),
@@ -61,18 +61,18 @@ class EditBillViewModel : ViewModel() {
val amountAsDouble: Double val amountAsDouble: Double
get() = parseAmountFromUi(amount) get() = parseAmountFromUi(amount)
fun getEvenSplit(): Double { val hasDifferentWeights: Boolean
val selectedOwersCount = owersSelection.count { it.value } get() = members.isNotEmpty() && members.any { it.weight != members[0].weight }
return if (selectedOwersCount > 0) SupportUtil.round2(amountAsDouble / selectedOwersCount) else 0.0
}
fun updateSplits() { fun updateSplits() {
if (splitMode == SplitMode.EVEN) { if (splitMode == SplitMode.EVEN) {
val even = getEvenSplit() val selectedMembers = members.filter { owersSelection[it.id] == true }
val evenStr = if (even == 0.0) "" else SupportUtil.round2(even).toString() val totalWeight = selectedMembers.sumOf { it.weight }
val uiAmount = amountAsDouble
members.forEach { m -> members.forEach { m ->
if (owersSelection[m.id] == true) { if (owersSelection[m.id] == true) {
owersCustomSplit[m.id] = evenStr val share = if (totalWeight > 0) (uiAmount * m.weight) / totalWeight else 0.0
owersCustomSplit[m.id] = if (share == 0.0) "" else SupportUtil.round2(share).toString()
} else { } else {
owersCustomSplit.remove(m.id) owersCustomSplit.remove(m.id)
} }
@@ -180,7 +180,7 @@ class EditBillViewModel : ViewModel() {
} }
fun getFinalAmount(): Double { fun getFinalAmount(): Double {
return SupportUtil.round2(amountAsDouble / selectedCurrencyRate) return SupportUtil.round2(amountAsDouble * selectedCurrencyRate)
} }
fun getFinalComment(): String { fun getFinalComment(): String {
@@ -252,10 +252,9 @@ class EditBillViewModel : ViewModel() {
if (selected) { if (selected) {
// If we have metadata, the custom splits from DB are also converted. // If we have metadata, the custom splits from DB are also converted.
// We should show them as "Original" if possible? // We should show them as "Original" if possible?
// Actually, if we use metadata, we should probably store original splits too, // Reverse calculation for display: UI Part = DB Part / Rate
// but for now let's just reverse the rate for display.
val dbPart = customSplits[member.id]!! val dbPart = customSplits[member.id]!!
val uiPart = if (selectedCurrencyRate != 1.0) dbPart * selectedCurrencyRate else dbPart val uiPart = if (selectedCurrencyRate != 0.0) dbPart / selectedCurrencyRate else dbPart
owersCustomSplit[member.id] = SupportUtil.round2(uiPart).toString() owersCustomSplit[member.id] = SupportUtil.round2(uiPart).toString()
} }
} }
@@ -263,18 +262,18 @@ class EditBillViewModel : ViewModel() {
// Even split logic // Even split logic
splitMode = SplitMode.EVEN splitMode = SplitMode.EVEN
val billOwerIds = bill.billOwersIds val billOwerIds = bill.billOwersIds
val selectedCount = billOwerIds.size val selectedMembers = members.filter { billOwerIds.contains(it.id) }
val totalWeight = selectedMembers.sumOf { it.weight }
// Use UI amount for even split calculation // Use UI amount for even split calculation
val uiAmount = amountAsDouble val uiAmount = amountAsDouble
val evenSplit = if (selectedCount > 0) uiAmount / selectedCount else 0.0
val evenSplitStr = if (evenSplit == 0.0) "" else SupportUtil.round2(evenSplit).toString()
for (member in members) { for (member in members) {
val selected = billOwerIds.contains(member.id) val selected = billOwerIds.contains(member.id)
owersSelection[member.id] = selected owersSelection[member.id] = selected
if (selected) { if (selected) {
owersCustomSplit[member.id] = evenSplitStr val share = if (totalWeight > 0) (uiAmount * member.weight) / totalWeight else 0.0
owersCustomSplit[member.id] = if (share == 0.0) "" else SupportUtil.round2(share).toString()
} }
} }
} }
@@ -114,7 +114,8 @@ class ManageCurrenciesActivity : AppCompatActivity() {
} }
private fun addOrUpdateCurrency() { private fun addOrUpdateCurrency() {
val exchangeRate = try { viewModel.newCurrencyRate.toDouble() } catch (_: Exception) { 0.0 } val uiRate = try { viewModel.newCurrencyRate.toDouble() } catch (_: Exception) { 0.0 }
val exchangeRate = if (uiRate != 0.0) 1.0 / uiRate else 0.0
val currencyName = viewModel.newCurrencyName val currencyName = viewModel.newCurrencyName
val editingId = viewModel.editingCurrencyId val editingId = viewModel.editingCurrencyId
@@ -142,7 +143,8 @@ class ManageCurrenciesActivity : AppCompatActivity() {
private fun startEditing(currency: DBCurrency) { private fun startEditing(currency: DBCurrency) {
viewModel.editingCurrencyId = currency.id viewModel.editingCurrencyId = currency.id
viewModel.newCurrencyName = currency.name ?: "" viewModel.newCurrencyName = currency.name ?: ""
viewModel.newCurrencyRate = currency.exchangeRate.toString() val uiRate = if (currency.exchangeRate != 0.0) 1.0 / currency.exchangeRate else 0.0
viewModel.newCurrencyRate = uiRate.toString()
} }
private fun cancelEditing() { private fun cancelEditing() {
@@ -304,7 +304,7 @@ fun CurrencyRow(
modifier = Modifier.padding(horizontal = 8.dp) modifier = Modifier.padding(horizontal = 8.dp)
) )
Text( Text(
text = formatAmount(currency.exchangeRate), text = formatAmount(if (currency.exchangeRate != 0.0) 1.0 / currency.exchangeRate else 0.0),
style = MaterialTheme.typography.body1, style = MaterialTheme.typography.body1,
fontWeight = FontWeight.ExtraBold, fontWeight = FontWeight.ExtraBold,
color = if (isEditing) MaterialTheme.colors.secondary else MaterialTheme.colors.onSurface color = if (isEditing) MaterialTheme.colors.secondary else MaterialTheme.colors.onSurface