Implemented showing account transactions
This commit is contained in:
parent
4061cf22e6
commit
32657331f9
|
@ -52,21 +52,28 @@ kotlin {
|
|||
|
||||
sourceSets {
|
||||
val desktopMain by getting
|
||||
|
||||
androidMain.dependencies {
|
||||
implementation(compose.preview)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
}
|
||||
|
||||
commonMain.dependencies {
|
||||
implementation(libs.kcsv)
|
||||
implementation(libs.klf)
|
||||
|
||||
// UI
|
||||
implementation(compose.runtime)
|
||||
implementation(compose.foundation)
|
||||
implementation(compose.material)
|
||||
implementation(compose.ui)
|
||||
implementation(compose.components.resources)
|
||||
implementation(compose.components.uiToolingPreview)
|
||||
|
||||
implementation(libs.androidx.lifecycle.viewmodel)
|
||||
implementation(libs.androidx.lifecycle.runtime.compose)
|
||||
}
|
||||
|
||||
androidMain.dependencies {
|
||||
implementation(compose.preview)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
}
|
||||
|
||||
desktopMain.dependencies {
|
||||
implementation(compose.desktop.currentOs)
|
||||
implementation(libs.kotlinx.coroutines.swing)
|
||||
|
|
|
@ -1,37 +1,43 @@
|
|||
package net.codinux.banking.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.material.Typography
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.sp
|
||||
import kotlinx.coroutines.launch
|
||||
import net.codinux.banking.client.model.AccountTransaction
|
||||
import net.codinux.banking.ui.composables.TransactionsList
|
||||
import net.codinux.banking.ui.service.BankingService
|
||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||
|
||||
import bankmeister.composeapp.generated.resources.Res
|
||||
import bankmeister.composeapp.generated.resources.compose_multiplatform
|
||||
private val typography = Typography(
|
||||
body1 = TextStyle(fontSize = 14.sp)
|
||||
)
|
||||
|
||||
private val bankService = BankingService()
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun App() {
|
||||
MaterialTheme {
|
||||
var showContent by remember { mutableStateOf(false) }
|
||||
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Button(onClick = { showContent = !showContent }) {
|
||||
Text("Click me!")
|
||||
}
|
||||
AnimatedVisibility(showContent) {
|
||||
val greeting = remember { Greeting().greet() }
|
||||
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Image(painterResource(Res.drawable.compose_multiplatform), null)
|
||||
Text("Compose: $greeting")
|
||||
}
|
||||
}
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val (transactions, setTransaction) = remember { mutableStateOf<List<AccountTransaction>>(emptyList()) }
|
||||
|
||||
coroutineScope.launch {
|
||||
setTransaction(bankService.getTransactions())
|
||||
}
|
||||
|
||||
MaterialTheme(typography = typography) {
|
||||
Column(Modifier.fillMaxWidth().fillMaxHeight(), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
TransactionsList(transactions)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package net.codinux.banking.ui.composables
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.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.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import net.codinux.banking.client.model.AccountTransaction
|
||||
import net.codinux.banking.ui.service.FormatUtil
|
||||
|
||||
private val formatUtil = FormatUtil()
|
||||
|
||||
@Composable
|
||||
fun TransactionListItem(transaction: AccountTransaction, backgroundColor: Color) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
.background(color = backgroundColor)
|
||||
.padding(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(
|
||||
text = transaction.otherPartyName ?: "",
|
||||
Modifier.fillMaxWidth(),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
Text(
|
||||
text = transaction.reference,
|
||||
Modifier.fillMaxWidth(),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
Column(Modifier.width(90.dp), horizontalAlignment = Alignment.End, verticalArrangement = Arrangement.Center) {
|
||||
Text(
|
||||
text = formatUtil.formatAmount(transaction.amount, transaction.currency),
|
||||
color = formatUtil.getColorForAmount(transaction.amount),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Text(
|
||||
text = formatUtil.formatDate(transaction.valueDate)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package net.codinux.banking.ui.composables
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import net.codinux.banking.client.model.AccountTransaction
|
||||
import net.codinux.banking.ui.service.Colors
|
||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||
|
||||
@Composable
|
||||
fun TransactionsList(transactions: List<AccountTransaction>) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.padding(4.dp)
|
||||
) {
|
||||
itemsIndexed(transactions) { index, transaction ->
|
||||
TransactionListItem(transaction, if (index % 2 == 0) Colors.Zinc100_50 else Color.White)
|
||||
|
||||
if (index < transactions.size) {
|
||||
Divider(color = Colors.Zinc200, thickness = 1.dp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun TransactionsListPreview() {
|
||||
MaterialTheme {
|
||||
TransactionsList(emptyList())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package net.codinux.banking.ui.service
|
||||
|
||||
import bankmeister.composeapp.generated.resources.Res
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.codinux.banking.client.model.AccountTransaction
|
||||
import net.codinux.banking.client.model.Amount
|
||||
import net.codinux.csv.reader.CsvReader
|
||||
import net.codinux.log.logger
|
||||
import org.jetbrains.compose.resources.ExperimentalResourceApi
|
||||
|
||||
@OptIn(ExperimentalResourceApi::class)
|
||||
class BankingService {
|
||||
|
||||
private var cachedTransactions: List<AccountTransaction>? = null
|
||||
|
||||
private val log by logger()
|
||||
|
||||
|
||||
suspend fun getTransactions(): List<AccountTransaction> {
|
||||
cachedTransactions?.let {
|
||||
return it
|
||||
}
|
||||
|
||||
val transactions = readTransactionsFromCsv()
|
||||
cachedTransactions = readTransactionsFromCsv()
|
||||
|
||||
return transactions
|
||||
}
|
||||
|
||||
private suspend fun readTransactionsFromCsv(): List<AccountTransaction> {
|
||||
val csv = Res.readBytes("files/transactions.csv").decodeToString()
|
||||
val csvReader = CsvReader(hasHeaderRow = true, reuseRowInstance = true, skipEmptyRows = true).read(csv)
|
||||
|
||||
return csvReader.mapNotNull { row ->
|
||||
try {
|
||||
AccountTransaction(
|
||||
Amount(row.getString("Amount")), row.getString("Currency"), row.getString("Reference"),
|
||||
LocalDate.parse(row.getString("BookingDate")), LocalDate.parse(row.getString("ValueDate")),
|
||||
row.getStringOrNull("OtherPartyName"), row.getStringOrNull("OtherPartyBankCode"), row.getStringOrNull("OtherPartyAccountId"),
|
||||
row.getString("BookingText")
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
log.error(e) { "Could not map row: ${row.fields}" }
|
||||
null
|
||||
}
|
||||
}
|
||||
.sortedByDescending { it.valueDate }
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package net.codinux.banking.ui.service
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
object Colors {
|
||||
|
||||
val Zinc100 = Color(244, 244, 245)
|
||||
val Zinc100_50 = Zinc100.copy(alpha = 0.5f)
|
||||
|
||||
val Zinc200 = Color(228, 228, 231)
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package net.codinux.banking.ui.service
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.codinux.banking.client.model.Amount
|
||||
import net.codinux.banking.fints.extensions.toStringWithMinDigits
|
||||
|
||||
class FormatUtil {
|
||||
|
||||
fun formatDate(date: LocalDate): String = // TODO: find a better way
|
||||
"${date.dayOfMonth.toStringWithMinDigits(2)}.${date.monthNumber.toStringWithMinDigits(2)}.${date.year.toString().substring(2)}"
|
||||
|
||||
fun formatAmount(amount: Amount, currency: String): String { // TODO: find a better way
|
||||
val parts = amount.amount.split('.')
|
||||
val decimalPart = if (parts.size == 2) parts[1] else "00"
|
||||
|
||||
return "${parts[0]},${decimalPart.padEnd(2, '0')} ${formatCurrency(currency)}"
|
||||
}
|
||||
|
||||
fun formatCurrency(currency: String): String = when (currency) {
|
||||
"EUR" -> "€"
|
||||
else -> currency
|
||||
}
|
||||
|
||||
fun getColorForAmount(amount: Amount): Color = when {
|
||||
amount.amount.startsWith("-") -> Color.Red
|
||||
else -> Color.Green
|
||||
}
|
||||
|
||||
}
|
|
@ -2,6 +2,9 @@
|
|||
kotlin = "2.0.10"
|
||||
kotlinx-coroutines = "1.8.1"
|
||||
|
||||
kcsv = "2.1.1"
|
||||
klf = "1.5.1"
|
||||
|
||||
agp = "8.2.2"
|
||||
android-compileSdk = "34"
|
||||
android-minSdk = "24"
|
||||
|
@ -19,6 +22,9 @@ compose-plugin = "1.6.11"
|
|||
junit = "4.13.2"
|
||||
|
||||
[libraries]
|
||||
kcsv = { group = "net.codinux.csv", name = "kcsv", version.ref = "kcsv" }
|
||||
klf = { group = "net.codinux.log", name = "kmp-log", version.ref = "klf" }
|
||||
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core-ktx" }
|
||||
androidx-test-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-junit" }
|
||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidx-espresso-core" }
|
||||
|
|
Loading…
Reference in New Issue