Implemented showing account transactions

This commit is contained in:
dankito 2024-08-25 01:43:24 +02:00
parent 4061cf22e6
commit 32657331f9
8 changed files with 234 additions and 26 deletions

View File

@ -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)

View File

@ -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)
}
}
}

View File

@ -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)
)
}
}
}

View File

@ -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())
}
}

View File

@ -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 }
}
}

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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" }