better currency handling
This commit is contained in:
@@ -27,6 +27,7 @@ import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
|||||||
import net.helcel.cowspent.theme.ThemeUtils
|
import net.helcel.cowspent.theme.ThemeUtils
|
||||||
import net.helcel.cowspent.util.BillParser
|
import net.helcel.cowspent.util.BillParser
|
||||||
import net.helcel.cowspent.util.CategoryUtils
|
import net.helcel.cowspent.util.CategoryUtils
|
||||||
|
import net.helcel.cowspent.util.SupportUtil
|
||||||
import java.text.ParseException
|
import java.text.ParseException
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
@@ -107,7 +108,8 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
val createIntent = Intent(this@EditBillActivity, QrCodeScannerActivity::class.java)
|
val createIntent = Intent(this@EditBillActivity, QrCodeScannerActivity::class.java)
|
||||||
scanQRCodeLauncher.launch(createIntent)
|
scanQRCodeLauncher.launch(createIntent)
|
||||||
},
|
},
|
||||||
onDelete = if (bill.id > 0) { { deleteBillAsked() } } else null,
|
onDuplicate = if (!viewModel.isNewBill) { { duplicateCurrentBill() } } else null,
|
||||||
|
onDelete = if (!viewModel.isNewBill) { { deleteBillAsked() } } else null,
|
||||||
accessLevel = db.getProject(bill.projectId)?.myAccessLevel ?: DBProject.ACCESS_LEVEL_ADMIN
|
accessLevel = db.getProject(bill.projectId)?.myAccessLevel ?: DBProject.ACCESS_LEVEL_ADMIN
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -191,6 +193,7 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
viewModel.currencies = currencies
|
viewModel.currencies = currencies
|
||||||
viewModel.mainCurrencyName = project?.currencyName ?: ""
|
viewModel.mainCurrencyName = project?.currencyName ?: ""
|
||||||
|
viewModel.isNewBill = (bill.id == 0L)
|
||||||
viewModel.initFromBill(bill, members, customSplits)
|
viewModel.initFromBill(bill, members, customSplits)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,6 +228,19 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun duplicateCurrentBill() {
|
||||||
|
bill = DBBill(
|
||||||
|
0, 0, bill.projectId, viewModel.payerId, viewModel.amountAsDouble,
|
||||||
|
System.currentTimeMillis() / 1000, viewModel.what, DBBill.STATE_ADDED,
|
||||||
|
viewModel.repeat, bill.paymentMode, viewModel.categoryRemoteId,
|
||||||
|
viewModel.getFinalComment(), viewModel.paymentModeRemoteId
|
||||||
|
)
|
||||||
|
viewModel.timestamp = bill.timestamp
|
||||||
|
viewModel.isNewBill = true
|
||||||
|
|
||||||
|
showToast(this, "Duplicating bill...")
|
||||||
|
}
|
||||||
|
|
||||||
private fun onBack() {
|
private fun onBack() {
|
||||||
if (!valuesHaveChanged()) {
|
if (!valuesHaveChanged()) {
|
||||||
finish()
|
finish()
|
||||||
@@ -298,9 +314,9 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
return !(bill.what == viewModel.what &&
|
return !(bill.what == viewModel.what &&
|
||||||
bill.timestamp == viewModel.timestamp &&
|
bill.timestamp == viewModel.timestamp &&
|
||||||
bill.amount == viewModel.amountAsDouble &&
|
bill.amount == viewModel.getFinalAmount() &&
|
||||||
bill.payerId == viewModel.payerId &&
|
bill.payerId == viewModel.payerId &&
|
||||||
bill.comment == viewModel.comment &&
|
bill.comment == viewModel.getFinalComment() &&
|
||||||
bill.repeat == viewModel.repeat &&
|
bill.repeat == viewModel.repeat &&
|
||||||
bill.categoryRemoteId == viewModel.categoryRemoteId &&
|
bill.categoryRemoteId == viewModel.categoryRemoteId &&
|
||||||
bill.paymentModeRemoteId == viewModel.paymentModeRemoteId &&
|
bill.paymentModeRemoteId == viewModel.paymentModeRemoteId &&
|
||||||
@@ -312,13 +328,17 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
val isCustomSplit = viewModel.isCustomSplit
|
val isCustomSplit = viewModel.isCustomSplit
|
||||||
|
|
||||||
if (isCustomSplit) {
|
if (isCustomSplit) {
|
||||||
val splits = viewModel.owersCustomSplit.filter { (id, amount) ->
|
val splits: Map<Long, Double> = viewModel.owersCustomSplit.filter { (id, amountStr) ->
|
||||||
viewModel.owersSelection[id] == true && (amount.replace(',', '.').toDoubleOrNull()
|
viewModel.owersSelection[id] == true && (amountStr.replace(',', '.').toDoubleOrNull()
|
||||||
?: 0.0) > 0
|
?: 0.0) > 0
|
||||||
}.mapValues { it.value.replace(',', '.').toDoubleOrNull() ?: 0.0 }
|
}.mapValues {
|
||||||
|
val uiAmount = it.value.replace(',', '.').toDoubleOrNull() ?: 0.0
|
||||||
|
SupportUtil.round2(uiAmount / viewModel.selectedCurrencyRate)
|
||||||
|
}
|
||||||
|
|
||||||
if (splits.isEmpty()) return@withContext 0L
|
if (splits.isEmpty()) return@withContext 0L
|
||||||
|
|
||||||
|
val finalComment = viewModel.getFinalComment()
|
||||||
val splitEntries = splits.entries.toList()
|
val splitEntries = splits.entries.toList()
|
||||||
|
|
||||||
// Pool of existing bills in this group that we can potentially reuse
|
// Pool of existing bills in this group that we can potentially reuse
|
||||||
@@ -360,7 +380,7 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
existingBill.paymentMode,
|
existingBill.paymentMode,
|
||||||
viewModel.paymentModeRemoteId,
|
viewModel.paymentModeRemoteId,
|
||||||
viewModel.categoryRemoteId,
|
viewModel.categoryRemoteId,
|
||||||
viewModel.comment
|
finalComment
|
||||||
)
|
)
|
||||||
if (firstSavedId == 0L) firstSavedId = billToUseId
|
if (firstSavedId == 0L) firstSavedId = billToUseId
|
||||||
} else {
|
} else {
|
||||||
@@ -368,7 +388,7 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
val newBill = DBBill(
|
val newBill = DBBill(
|
||||||
0, 0, bill.projectId, viewModel.payerId, amount,
|
0, 0, bill.projectId, viewModel.payerId, amount,
|
||||||
viewModel.timestamp, viewModel.what, DBBill.STATE_ADDED, viewModel.repeat,
|
viewModel.timestamp, viewModel.what, DBBill.STATE_ADDED, viewModel.repeat,
|
||||||
bill.paymentMode, viewModel.categoryRemoteId, viewModel.comment, viewModel.paymentModeRemoteId
|
bill.paymentMode, viewModel.categoryRemoteId, finalComment, viewModel.paymentModeRemoteId
|
||||||
)
|
)
|
||||||
newBill.billOwers = listOf(DBBillOwer(0, 0, memberId))
|
newBill.billOwers = listOf(DBBillOwer(0, 0, memberId))
|
||||||
val newId = db.addBill(newBill)
|
val newId = db.addBill(newBill)
|
||||||
@@ -398,7 +418,8 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
return@withContext firstSavedId
|
return@withContext firstSavedId
|
||||||
} else {
|
} else {
|
||||||
val newAmount = viewModel.amountAsDouble
|
val newAmount = viewModel.getFinalAmount()
|
||||||
|
val finalComment = viewModel.getFinalComment()
|
||||||
val newOwersIds = viewModel.getOwersIds()
|
val newOwersIds = viewModel.getOwersIds()
|
||||||
|
|
||||||
if (bill.id != 0L) {
|
if (bill.id != 0L) {
|
||||||
@@ -414,7 +435,7 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
bill.paymentMode,
|
bill.paymentMode,
|
||||||
viewModel.paymentModeRemoteId,
|
viewModel.paymentModeRemoteId,
|
||||||
viewModel.categoryRemoteId,
|
viewModel.categoryRemoteId,
|
||||||
viewModel.comment
|
finalComment
|
||||||
)
|
)
|
||||||
if (groupedBillIds != null) {
|
if (groupedBillIds != null) {
|
||||||
for (id in groupedBillIds) {
|
for (id in groupedBillIds) {
|
||||||
@@ -431,7 +452,7 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
val newBill = DBBill(
|
val newBill = DBBill(
|
||||||
0, 0, bill.projectId, viewModel.payerId, newAmount,
|
0, 0, bill.projectId, viewModel.payerId, newAmount,
|
||||||
viewModel.timestamp, viewModel.what, DBBill.STATE_ADDED, viewModel.repeat,
|
viewModel.timestamp, viewModel.what, DBBill.STATE_ADDED, viewModel.repeat,
|
||||||
bill.paymentMode, viewModel.categoryRemoteId, viewModel.comment, viewModel.paymentModeRemoteId
|
bill.paymentMode, viewModel.categoryRemoteId, finalComment, viewModel.paymentModeRemoteId
|
||||||
)
|
)
|
||||||
newOwersIds.forEach { newBill.billOwers += DBBillOwer(0, 0, it) }
|
newOwersIds.forEach { newBill.billOwers += DBBillOwer(0, 0, it) }
|
||||||
val newBillId = db.addBill(newBill)
|
val newBillId = db.addBill(newBill)
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ fun EditBillScreen(
|
|||||||
onDateClick: () -> Unit,
|
onDateClick: () -> Unit,
|
||||||
onTimeClick: () -> Unit,
|
onTimeClick: () -> Unit,
|
||||||
onScan: () -> Unit,
|
onScan: () -> Unit,
|
||||||
|
onDuplicate: (() -> Unit)? = null,
|
||||||
onDelete: (() -> Unit)? = null,
|
onDelete: (() -> Unit)? = null,
|
||||||
accessLevel: Int = DBProject.ACCESS_LEVEL_ADMIN
|
accessLevel: Int = DBProject.ACCESS_LEVEL_ADMIN
|
||||||
) {
|
) {
|
||||||
@@ -59,7 +60,7 @@ fun EditBillScreen(
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(stringResource(if (viewModel.what.isEmpty()) R.string.simple_new_bill else R.string.simple_edit_bill)) },
|
title = { Text(stringResource(if (viewModel.isNewBill) R.string.simple_new_bill else R.string.simple_edit_bill)) },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
||||||
@@ -70,6 +71,11 @@ fun EditBillScreen(
|
|||||||
IconButton(onClick = onScan) {
|
IconButton(onClick = onScan) {
|
||||||
Icon(Icons.Default.QrCodeScanner, contentDescription = null)
|
Icon(Icons.Default.QrCodeScanner, contentDescription = null)
|
||||||
}
|
}
|
||||||
|
if (onDuplicate != null) {
|
||||||
|
IconButton(onClick = onDuplicate) {
|
||||||
|
Icon(Icons.Default.ContentCopy, contentDescription = "Duplicate")
|
||||||
|
}
|
||||||
|
}
|
||||||
if (onDelete != null) {
|
if (onDelete != null) {
|
||||||
IconButton(onClick = onDelete) {
|
IconButton(onClick = onDelete) {
|
||||||
Icon(Icons.Default.Delete, contentDescription = stringResource(R.string.action_delete))
|
Icon(Icons.Default.Delete, contentDescription = stringResource(R.string.action_delete))
|
||||||
@@ -160,7 +166,6 @@ fun BillBasicInfoSection(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val currencyDialogTitle =
|
val currencyDialogTitle =
|
||||||
stringResource(R.string.currency_dialog_title, viewModel.mainCurrencyName)
|
stringResource(R.string.currency_dialog_title, viewModel.mainCurrencyName)
|
||||||
val noCurrencyError = stringResource(R.string.no_currency_error)
|
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = viewModel.what,
|
value = viewModel.what,
|
||||||
@@ -182,25 +187,37 @@ fun BillBasicInfoSection(
|
|||||||
enabled = canEdit,
|
enabled = canEdit,
|
||||||
placeholder = { Text("0") },
|
placeholder = { Text("0") },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
leadingIcon = { Icon(Icons.Default.AttachMoney, contentDescription = null) },
|
leadingIcon = {
|
||||||
|
val currencyToShow = viewModel.selectedCurrencyName.ifEmpty { viewModel.mainCurrencyName }
|
||||||
|
TextIconDisplay(
|
||||||
|
textIcon = TextIcon.Symbol(currencyToShow),
|
||||||
|
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
},
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
IconButton(
|
IconButton(
|
||||||
enabled = canEdit,
|
enabled = canEdit,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (viewModel.currencies.isNotEmpty()) {
|
val mainLabel = viewModel.mainCurrencyName.ifEmpty { "Main" }
|
||||||
viewModel.showDialog(
|
val options = listOf("$mainLabel | Base") + viewModel.currencies.map {
|
||||||
title = currencyDialogTitle,
|
"${it.name} | 1 $mainLabel = ${it.exchangeRate} ${it.name}"
|
||||||
items = viewModel.currencies.map { "${it.name} (${it.exchangeRate})" },
|
|
||||||
onItemSelected = { index ->
|
|
||||||
viewModel.convertCurrency(viewModel.currencies[index])
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
showToast(context, noCurrencyError)
|
|
||||||
}
|
}
|
||||||
|
viewModel.showDialog(
|
||||||
|
title = currencyDialogTitle,
|
||||||
|
items = options,
|
||||||
|
onItemSelected = { index ->
|
||||||
|
if (index == 0) {
|
||||||
|
viewModel.selectedCurrencyName = ""
|
||||||
|
viewModel.selectedCurrencyRate = 1.0
|
||||||
|
viewModel.updateSplits()
|
||||||
|
} else {
|
||||||
|
viewModel.convertCurrency(viewModel.currencies[index - 1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.CurrencyExchange, contentDescription = null)
|
Icon(Icons.Default.SwapHoriz, contentDescription = null)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
|
||||||
@@ -587,6 +604,7 @@ fun EditBillScreenPreview() {
|
|||||||
viewModel = EditBillViewModel().apply {
|
viewModel = EditBillViewModel().apply {
|
||||||
what = "Pizza"
|
what = "Pizza"
|
||||||
amount = "12.50"
|
amount = "12.50"
|
||||||
|
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)
|
||||||
|
|||||||
@@ -25,9 +25,12 @@ class EditBillViewModel : ViewModel() {
|
|||||||
var repeat by mutableStateOf(DBBill.NON_REPEATED)
|
var repeat by mutableStateOf(DBBill.NON_REPEATED)
|
||||||
var paymentModeRemoteId by mutableIntStateOf(0)
|
var paymentModeRemoteId by mutableIntStateOf(0)
|
||||||
var categoryRemoteId by mutableIntStateOf(0)
|
var categoryRemoteId by mutableIntStateOf(0)
|
||||||
|
var isNewBill by mutableStateOf(false)
|
||||||
|
|
||||||
var currencies by mutableStateOf<List<DBCurrency>>(emptyList())
|
var currencies by mutableStateOf<List<DBCurrency>>(emptyList())
|
||||||
var mainCurrencyName by mutableStateOf("")
|
var mainCurrencyName by mutableStateOf("")
|
||||||
|
var selectedCurrencyName by mutableStateOf("")
|
||||||
|
var selectedCurrencyRate by mutableStateOf(1.0)
|
||||||
var members by mutableStateOf<List<DBMember>>(emptyList())
|
var members by mutableStateOf<List<DBMember>>(emptyList())
|
||||||
|
|
||||||
var owersSelection = mutableStateMapOf<Long, Boolean>()
|
var owersSelection = mutableStateMapOf<Long, Boolean>()
|
||||||
@@ -128,46 +131,77 @@ class EditBillViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun convertCurrency(currency: DBCurrency) {
|
fun convertCurrency(currency: DBCurrency) {
|
||||||
val originalAmountStr = amount
|
if (amountAsDouble == 0.0) return
|
||||||
val originalAmount = amountAsDouble
|
|
||||||
if (originalAmount == 0.0) return
|
|
||||||
|
|
||||||
val newAmount = originalAmount / currency.exchangeRate
|
|
||||||
amount = SupportUtil.round2(newAmount).toString()
|
|
||||||
|
|
||||||
val currencyLabel = currency.name ?: ""
|
|
||||||
val conversionNote = "($originalAmountStr $currencyLabel)"
|
|
||||||
if (!comment.contains(conversionNote)) {
|
|
||||||
if (comment.isNotEmpty() && !comment.endsWith(" ")) {
|
|
||||||
comment += " "
|
|
||||||
}
|
|
||||||
comment += conversionNote
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCustomSplit) {
|
|
||||||
owersCustomSplit.keys.toList().forEach { id ->
|
|
||||||
val value = owersCustomSplit[id] ?: ""
|
|
||||||
val partAmount = value.replace(',', '.').toDoubleOrNull() ?: 0.0
|
|
||||||
if (partAmount != 0.0) {
|
|
||||||
val newPartAmount = partAmount / currency.exchangeRate
|
|
||||||
owersCustomSplit[id] = SupportUtil.round2(newPartAmount).toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
selectedCurrencyName = currency.name ?: ""
|
||||||
|
selectedCurrencyRate = currency.exchangeRate
|
||||||
|
|
||||||
|
// We don't change 'amount' here anymore as per request.
|
||||||
|
// It stays as the original amount.
|
||||||
|
|
||||||
updateSplits()
|
updateSplits()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getFinalAmount(): Double {
|
||||||
|
return SupportUtil.round2(amountAsDouble / selectedCurrencyRate)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFinalComment(): String {
|
||||||
|
var baseComment = comment
|
||||||
|
val regex = "\\s*#[^:]+:[\\d.,]+@[\\d.,]+".toRegex()
|
||||||
|
baseComment = baseComment.replace(regex, "").trim()
|
||||||
|
|
||||||
|
return if (selectedCurrencyName.isNotEmpty() && selectedCurrencyRate != 1.0) {
|
||||||
|
val metadata = "#$selectedCurrencyName:$amount@$selectedCurrencyRate"
|
||||||
|
if (baseComment.isEmpty()) metadata else "$baseComment $metadata"
|
||||||
|
} else {
|
||||||
|
baseComment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun initFromBill(bill: DBBill, members: List<DBMember>, customSplits: Map<Long, Double>? = null) {
|
fun initFromBill(bill: DBBill, members: List<DBMember>, customSplits: Map<Long, Double>? = null) {
|
||||||
this.members = members
|
this.members = members
|
||||||
what = bill.what
|
what = bill.what
|
||||||
amount = if (bill.amount == 0.0) "" else bill.amount.toString()
|
|
||||||
comment = bill.comment ?: ""
|
|
||||||
timestamp = bill.timestamp
|
timestamp = bill.timestamp
|
||||||
payerId = bill.payerId
|
payerId = bill.payerId
|
||||||
repeat = bill.repeat ?: DBBill.NON_REPEATED
|
repeat = bill.repeat ?: DBBill.NON_REPEATED
|
||||||
paymentModeRemoteId = bill.paymentModeRemoteId
|
paymentModeRemoteId = bill.paymentModeRemoteId
|
||||||
categoryRemoteId = bill.categoryRemoteId
|
categoryRemoteId = bill.categoryRemoteId
|
||||||
|
|
||||||
|
val rawComment = bill.comment ?: ""
|
||||||
|
|
||||||
|
// Try to parse existing conversion metadata #CURR:ORIG@RATE
|
||||||
|
// Using [^:]+ for currency name to support symbols and names with spaces
|
||||||
|
val regex = "#([^:]+):([\\d.,]+)@([\\d.,]+)".toRegex()
|
||||||
|
val match = regex.find(rawComment)
|
||||||
|
|
||||||
|
if (match != null) {
|
||||||
|
val (currName, origAmount, rate) = match.destructured
|
||||||
|
selectedCurrencyName = currName
|
||||||
|
selectedCurrencyRate = rate.replace(',', '.').toDoubleOrNull() ?: 1.0
|
||||||
|
amount = origAmount
|
||||||
|
comment = rawComment.replace(match.value, "").trim()
|
||||||
|
|
||||||
|
// Check if latest rate is different
|
||||||
|
val latestCurrency = currencies.find { it.name == selectedCurrencyName }
|
||||||
|
if (latestCurrency != null && latestCurrency.exchangeRate != selectedCurrencyRate) {
|
||||||
|
showDialog(
|
||||||
|
title = "Update Exchange Rate?",
|
||||||
|
message = "The exchange rate for $selectedCurrencyName has changed from $selectedCurrencyRate to ${latestCurrency.exchangeRate}. Do you want to update the conversion for the saved total?",
|
||||||
|
positiveText = "Update Rate",
|
||||||
|
negativeText = "Keep Old",
|
||||||
|
onConfirm = {
|
||||||
|
selectedCurrencyRate = latestCurrency.exchangeRate
|
||||||
|
updateSplits()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectedCurrencyName = ""
|
||||||
|
selectedCurrencyRate = 1.0
|
||||||
|
amount = if (bill.amount == 0.0) "" else bill.amount.toString()
|
||||||
|
comment = rawComment
|
||||||
|
}
|
||||||
|
|
||||||
owersSelection.clear()
|
owersSelection.clear()
|
||||||
owersCustomSplit.clear()
|
owersCustomSplit.clear()
|
||||||
@@ -178,13 +212,23 @@ class EditBillViewModel : ViewModel() {
|
|||||||
val selected = customSplits.containsKey(member.id)
|
val selected = customSplits.containsKey(member.id)
|
||||||
owersSelection[member.id] = selected
|
owersSelection[member.id] = selected
|
||||||
if (selected) {
|
if (selected) {
|
||||||
owersCustomSplit[member.id] = SupportUtil.round2(customSplits[member.id]!!).toString()
|
// If we have metadata, the custom splits from DB are also converted.
|
||||||
|
// We should show them as "Original" if possible?
|
||||||
|
// Actually, if we use metadata, we should probably store original splits too,
|
||||||
|
// but for now let's just reverse the rate for display.
|
||||||
|
val dbPart = customSplits[member.id]!!
|
||||||
|
val uiPart = if (selectedCurrencyRate != 1.0) dbPart * selectedCurrencyRate else dbPart
|
||||||
|
owersCustomSplit[member.id] = SupportUtil.round2(uiPart).toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Even split logic
|
||||||
val billOwerIds = bill.billOwersIds
|
val billOwerIds = bill.billOwersIds
|
||||||
val selectedCount = billOwerIds.size
|
val selectedCount = billOwerIds.size
|
||||||
val evenSplit = if (selectedCount > 0) bill.amount / selectedCount else 0.0
|
|
||||||
|
// Use UI amount for even split calculation
|
||||||
|
val uiAmount = amountAsDouble
|
||||||
|
val evenSplit = if (selectedCount > 0) uiAmount / selectedCount else 0.0
|
||||||
val evenSplitStr = if (evenSplit == 0.0) "" else SupportUtil.round2(evenSplit).toString()
|
val evenSplitStr = if (evenSplit == 0.0) "" else SupportUtil.round2(evenSplit).toString()
|
||||||
|
|
||||||
for (member in members) {
|
for (member in members) {
|
||||||
|
|||||||
+12
-5
@@ -15,6 +15,7 @@ import net.helcel.cowspent.R
|
|||||||
import net.helcel.cowspent.android.helper.showToast
|
import net.helcel.cowspent.android.helper.showToast
|
||||||
import net.helcel.cowspent.model.DBBill
|
import net.helcel.cowspent.model.DBBill
|
||||||
import net.helcel.cowspent.model.DBCurrency
|
import net.helcel.cowspent.model.DBCurrency
|
||||||
|
import net.helcel.cowspent.model.ProjectType
|
||||||
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
||||||
import net.helcel.cowspent.theme.ThemeUtils
|
import net.helcel.cowspent.theme.ThemeUtils
|
||||||
import net.helcel.cowspent.util.ICallback
|
import net.helcel.cowspent.util.ICallback
|
||||||
@@ -86,11 +87,17 @@ class ManageCurrenciesActivity : AppCompatActivity() {
|
|||||||
val project = db!!.getProject(selectedProjectID)
|
val project = db!!.getProject(selectedProjectID)
|
||||||
if (project != null) {
|
if (project != null) {
|
||||||
db!!.syncIfRemote(project)
|
db!!.syncIfRemote(project)
|
||||||
withContext(Dispatchers.Main) {
|
if (project.type == ProjectType.COSPEND) {
|
||||||
if (!db!!.cowspentServerSyncHelper
|
withContext(Dispatchers.Main) {
|
||||||
.editRemoteProject(selectedProjectID, project.name, null, null, newMainCurrencyName, editMainCurrencyCallBack)
|
if (!db!!.cowspentServerSyncHelper
|
||||||
) {
|
.editRemoteProject(selectedProjectID, project.name, null, null, newMainCurrencyName, editMainCurrencyCallBack)
|
||||||
showToast(this@ManageCurrenciesActivity, getString(R.string.remote_project_operation_no_network), Toast.LENGTH_LONG)
|
) {
|
||||||
|
showToast(this@ManageCurrenciesActivity, getString(R.string.remote_project_operation_no_network), Toast.LENGTH_LONG)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
showToast(this@ManageCurrenciesActivity, getString(R.string.currency_saved_success), Toast.LENGTH_LONG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
|||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
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.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
@@ -12,6 +13,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import net.helcel.cowspent.R
|
import net.helcel.cowspent.R
|
||||||
@@ -93,19 +95,42 @@ fun ManageCurrenciesScreen(
|
|||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
Text(stringResource(R.string.add_currency_title), style = MaterialTheme.typography.h6)
|
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
|
||||||
|
) {
|
||||||
|
Text("1 ", style = MaterialTheme.typography.body1)
|
||||||
|
Text(viewModel.mainCurrencyName.ifEmpty { "Base" }, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = viewModel.newCurrencyName,
|
value = viewModel.newCurrencyName,
|
||||||
onValueChange = { viewModel.newCurrencyName = it },
|
onValueChange = { viewModel.newCurrencyName = it },
|
||||||
label = { Text(stringResource(R.string.currency_edit_name)) },
|
label = { Text(stringResource(R.string.currency_edit_name)) },
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f),
|
||||||
|
placeholder = { Text("e.g. USD") }
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = viewModel.newCurrencyRate,
|
value = viewModel.newCurrencyRate,
|
||||||
onValueChange = { viewModel.newCurrencyRate = it },
|
onValueChange = { viewModel.newCurrencyRate = it },
|
||||||
label = { Text(stringResource(R.string.currency_rate)) },
|
label = { Text(stringResource(R.string.currency_rate)) },
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f),
|
||||||
|
placeholder = { Text("1.0") }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
@@ -121,7 +146,7 @@ fun ManageCurrenciesScreen(
|
|||||||
|
|
||||||
LazyColumn(modifier = Modifier.weight(1f)) {
|
LazyColumn(modifier = Modifier.weight(1f)) {
|
||||||
items(viewModel.currencies) { currency ->
|
items(viewModel.currencies) { currency ->
|
||||||
CurrencyRow(currency, onDelete = { onDelete(currency) })
|
CurrencyRow(currency, viewModel.mainCurrencyName, onDelete = { onDelete(currency) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,15 +154,47 @@ fun ManageCurrenciesScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CurrencyRow(currency: DBCurrency, onDelete: () -> Unit) {
|
fun CurrencyRow(currency: DBCurrency, mainCurrencyName: String, onDelete: () -> Unit) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
|
modifier = Modifier.fillMaxWidth().padding(vertical = 12.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
// Table-like layout
|
||||||
Text(currency.name ?: "", style = MaterialTheme.typography.subtitle1)
|
Row(
|
||||||
Text("Rate: ${currency.exchangeRate}", style = MaterialTheme.typography.caption)
|
modifier = Modifier.weight(1f),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "1 ",
|
||||||
|
style = MaterialTheme.typography.body1,
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = mainCurrencyName,
|
||||||
|
style = MaterialTheme.typography.body1,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = " = ",
|
||||||
|
style = MaterialTheme.typography.h6,
|
||||||
|
color = MaterialTheme.colors.primary,
|
||||||
|
modifier = Modifier.padding(horizontal = 8.dp)
|
||||||
|
)
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = currency.exchangeRate.toString(),
|
||||||
|
style = MaterialTheme.typography.body1,
|
||||||
|
fontWeight = FontWeight.ExtraBold
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = currency.name ?: "",
|
||||||
|
style = MaterialTheme.typography.caption,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IconButton(onClick = onDelete) {
|
IconButton(onClick = onDelete) {
|
||||||
Icon(Icons.Default.Delete, contentDescription = null, tint = MaterialTheme.colors.error)
|
Icon(Icons.Default.Delete, contentDescription = null, tint = MaterialTheme.colors.error)
|
||||||
}
|
}
|
||||||
@@ -151,6 +208,7 @@ fun CurrencyRowPreview() {
|
|||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
CurrencyRow(
|
CurrencyRow(
|
||||||
currency = DBCurrency(1, 0, 0, "USD", 1.0, 0),
|
currency = DBCurrency(1, 0, 0, "USD", 1.0, 0),
|
||||||
|
mainCurrencyName = "EUR",
|
||||||
onDelete = {}
|
onDelete = {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.compose.material.*
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.scale
|
import androidx.compose.ui.draw.scale
|
||||||
@@ -26,6 +27,8 @@ import net.helcel.cowspent.R
|
|||||||
import net.helcel.cowspent.android.helper.UserAvatar
|
import net.helcel.cowspent.android.helper.UserAvatar
|
||||||
import net.helcel.cowspent.android.helper.formatBalance
|
import net.helcel.cowspent.android.helper.formatBalance
|
||||||
import net.helcel.cowspent.android.helper.lazyVerticalScrollbar
|
import net.helcel.cowspent.android.helper.lazyVerticalScrollbar
|
||||||
|
import net.helcel.cowspent.android.helper.TextIcon
|
||||||
|
import net.helcel.cowspent.android.helper.TextIconDisplay
|
||||||
import net.helcel.cowspent.model.DBMember
|
import net.helcel.cowspent.model.DBMember
|
||||||
import net.helcel.cowspent.model.DBProject
|
import net.helcel.cowspent.model.DBProject
|
||||||
import net.helcel.cowspent.model.ProjectType
|
import net.helcel.cowspent.model.ProjectType
|
||||||
@@ -38,6 +41,7 @@ fun Drawer(
|
|||||||
selectedProjectId: Long,
|
selectedProjectId: Long,
|
||||||
selectedMemberId: Long?,
|
selectedMemberId: Long?,
|
||||||
lastSyncText: String,
|
lastSyncText: String,
|
||||||
|
mainCurrency: String? = null,
|
||||||
showArchived: Boolean = false,
|
showArchived: Boolean = false,
|
||||||
onProjectClick: (Long) -> Unit,
|
onProjectClick: (Long) -> Unit,
|
||||||
onProjectOptionsClick: (Long) -> Unit,
|
onProjectOptionsClick: (Long) -> Unit,
|
||||||
@@ -96,22 +100,37 @@ fun Drawer(
|
|||||||
|
|
||||||
// Members Section
|
// Members Section
|
||||||
val membersState = rememberLazyListState()
|
val membersState = rememberLazyListState()
|
||||||
|
val sortedMembers = remember(members, memberBalances) {
|
||||||
|
members.sortedWith(compareBy<DBMember> {
|
||||||
|
val balance = memberBalances[it.id] ?: 0.0
|
||||||
|
when {
|
||||||
|
balance > 0.01 -> 0
|
||||||
|
balance < -0.01 -> 1
|
||||||
|
else -> 2
|
||||||
|
}
|
||||||
|
}.thenBy {
|
||||||
|
val balance = memberBalances[it.id] ?: 0.0
|
||||||
|
if (balance > 0.01) -balance else balance
|
||||||
|
}.thenBy { it.name })
|
||||||
|
}
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
state = membersState,
|
state = membersState,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.heightIn(max = maxBottomHeight)
|
.heightIn(max = maxBottomHeight)
|
||||||
.lazyVerticalScrollbar(membersState)
|
.lazyVerticalScrollbar(membersState)
|
||||||
) {
|
) {
|
||||||
if (members.isNotEmpty()) {
|
if (sortedMembers.isNotEmpty()) {
|
||||||
item {
|
item {
|
||||||
DrawerItem(
|
DrawerItem(
|
||||||
icon = Icons.Default.Receipt,
|
icon = Icons.Default.Receipt,
|
||||||
text = stringResource(R.string.label_all_bills),
|
text = stringResource(R.string.label_all_bills),
|
||||||
|
secondaryText = mainCurrency,
|
||||||
selected = selectedMemberId == null,
|
selected = selectedMemberId == null,
|
||||||
onClick = { onMemberClick(null) }
|
onClick = { onMemberClick(null) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
items(members) { member ->
|
items(sortedMembers) { member ->
|
||||||
val balance = memberBalances[member.id] ?: 0.0
|
val balance = memberBalances[member.id] ?: 0.0
|
||||||
MemberDrawerItem(
|
MemberDrawerItem(
|
||||||
member = member,
|
member = member,
|
||||||
@@ -241,6 +260,7 @@ fun DrawerItem(
|
|||||||
member: DBMember? = null,
|
member: DBMember? = null,
|
||||||
text: String? = null,
|
text: String? = null,
|
||||||
balanceText: String? = null,
|
balanceText: String? = null,
|
||||||
|
secondaryText: String? = null,
|
||||||
balanceColor: Color = Color.Unspecified,
|
balanceColor: Color = Color.Unspecified,
|
||||||
selected: Boolean = false,
|
selected: Boolean = false,
|
||||||
alpha: Float = 1f,
|
alpha: Float = 1f,
|
||||||
@@ -294,6 +314,17 @@ fun DrawerItem(
|
|||||||
modifier = Modifier.padding(end = 8.dp)
|
modifier = Modifier.padding(end = 8.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (secondaryText != null) {
|
||||||
|
Box(modifier = Modifier
|
||||||
|
.padding(end = 8.dp)
|
||||||
|
) {
|
||||||
|
TextIconDisplay(
|
||||||
|
textIcon = TextIcon.Symbol(secondaryText),
|
||||||
|
tint = contentColor.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (onSecondaryClick != null) {
|
if (onSecondaryClick != null) {
|
||||||
IconButton(onClick = onSecondaryClick) {
|
IconButton(onClick = onSecondaryClick) {
|
||||||
@@ -340,6 +371,7 @@ fun DrawerPreview() {
|
|||||||
selectedProjectId = 1,
|
selectedProjectId = 1,
|
||||||
selectedMemberId = null,
|
selectedMemberId = null,
|
||||||
lastSyncText = "Last sync: 5 mins ago",
|
lastSyncText = "Last sync: 5 mins ago",
|
||||||
|
mainCurrency = "EUR",
|
||||||
onProjectClick = {},
|
onProjectClick = {},
|
||||||
onProjectOptionsClick = {},
|
onProjectOptionsClick = {},
|
||||||
onMemberClick = {},
|
onMemberClick = {},
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
@@ -128,10 +129,32 @@ fun AlertDialog(
|
|||||||
)
|
)
|
||||||
Spacer(Modifier.width(16.dp))
|
Spacer(Modifier.width(16.dp))
|
||||||
}
|
}
|
||||||
Text(
|
|
||||||
text = item.toString(),
|
val text = item.toString()
|
||||||
style = MaterialTheme.typography.body1
|
if (text.contains("|")) {
|
||||||
)
|
val parts = text.split("|")
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = parts[0].trim(),
|
||||||
|
style = MaterialTheme.typography.body1,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = parts[1].trim(),
|
||||||
|
style = MaterialTheme.typography.body2,
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.body1
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -234,10 +257,32 @@ fun AlertDialogContent(
|
|||||||
)
|
)
|
||||||
Spacer(Modifier.width(16.dp))
|
Spacer(Modifier.width(16.dp))
|
||||||
}
|
}
|
||||||
Text(
|
|
||||||
text = item.toString(),
|
val text = item.toString()
|
||||||
style = MaterialTheme.typography.body1
|
if (text.contains("|")) {
|
||||||
)
|
val parts = text.split("|")
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = parts[0].trim(),
|
||||||
|
style = MaterialTheme.typography.body1,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = parts[1].trim(),
|
||||||
|
style = MaterialTheme.typography.body2,
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.body1
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package net.helcel.cowspent.android.helper
|
||||||
|
|
||||||
|
import androidx.compose.material.LocalContentColor
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
|
||||||
|
sealed class TextIcon {
|
||||||
|
data class Symbol(val text: String) : TextIcon()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper Composable to display a [TextIcon].
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun TextIconDisplay(
|
||||||
|
textIcon: TextIcon,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
tint: Color = LocalContentColor.current
|
||||||
|
) {
|
||||||
|
when (textIcon) {
|
||||||
|
is TextIcon.Symbol -> Text(
|
||||||
|
text = textIcon.text,
|
||||||
|
modifier = modifier,
|
||||||
|
color = tint,
|
||||||
|
style = MaterialTheme.typography.body1,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -444,11 +444,11 @@ class BillsListViewActivity :
|
|||||||
fun onManageCurrenciesClick(projectId: Long) {
|
fun onManageCurrenciesClick(projectId: Long) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val proj = withContext(Dispatchers.IO) { db.getProject(projectId) }
|
val proj = withContext(Dispatchers.IO) { db.getProject(projectId) }
|
||||||
if (proj != null && proj.type == ProjectType.COSPEND) {
|
if (proj != null) {
|
||||||
startActivity(Intent(applicationContext, ManageCurrenciesActivity::class.java).apply {
|
startActivity(Intent(applicationContext, ManageCurrenciesActivity::class.java).apply {
|
||||||
putExtra(ManageCurrenciesActivity.EXTRA_PROJECT_ID, projectId)
|
putExtra(ManageCurrenciesActivity.EXTRA_PROJECT_ID, projectId)
|
||||||
})
|
})
|
||||||
} else showToast(this@BillsListViewActivity, getString(R.string.currency_management_unavailable))
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -553,7 +553,11 @@ class CowspentSQLiteOpenHelper private constructor(val context: Context) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getCurrencies(projectId: Long): List<DBCurrency> {
|
fun getCurrencies(projectId: Long): List<DBCurrency> {
|
||||||
return getCurrenciesCustom("$key_projectid = ?", arrayOf(projectId.toString()), null)
|
return getCurrenciesCustom(
|
||||||
|
"$key_projectid = ? AND $key_state != ?",
|
||||||
|
arrayOf(projectId.toString(), DBBill.STATE_DELETED.toString()),
|
||||||
|
null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
|
|||||||
Reference in New Issue
Block a user