Implemented showing account transactions
This commit is contained in:
parent
4061cf22e6
commit
32657331f9
|
@ -52,21 +52,28 @@ kotlin {
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
val desktopMain by getting
|
val desktopMain by getting
|
||||||
|
|
||||||
androidMain.dependencies {
|
|
||||||
implementation(compose.preview)
|
|
||||||
implementation(libs.androidx.activity.compose)
|
|
||||||
}
|
|
||||||
commonMain.dependencies {
|
commonMain.dependencies {
|
||||||
|
implementation(libs.kcsv)
|
||||||
|
implementation(libs.klf)
|
||||||
|
|
||||||
|
// UI
|
||||||
implementation(compose.runtime)
|
implementation(compose.runtime)
|
||||||
implementation(compose.foundation)
|
implementation(compose.foundation)
|
||||||
implementation(compose.material)
|
implementation(compose.material)
|
||||||
implementation(compose.ui)
|
implementation(compose.ui)
|
||||||
implementation(compose.components.resources)
|
implementation(compose.components.resources)
|
||||||
implementation(compose.components.uiToolingPreview)
|
implementation(compose.components.uiToolingPreview)
|
||||||
|
|
||||||
implementation(libs.androidx.lifecycle.viewmodel)
|
implementation(libs.androidx.lifecycle.viewmodel)
|
||||||
implementation(libs.androidx.lifecycle.runtime.compose)
|
implementation(libs.androidx.lifecycle.runtime.compose)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
androidMain.dependencies {
|
||||||
|
implementation(compose.preview)
|
||||||
|
implementation(libs.androidx.activity.compose)
|
||||||
|
}
|
||||||
|
|
||||||
desktopMain.dependencies {
|
desktopMain.dependencies {
|
||||||
implementation(compose.desktop.currentOs)
|
implementation(compose.desktop.currentOs)
|
||||||
implementation(libs.kotlinx.coroutines.swing)
|
implementation(libs.kotlinx.coroutines.swing)
|
||||||
|
|
|
@ -1,37 +1,43 @@
|
||||||
package net.codinux.banking.ui
|
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.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.material.Button
|
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Typography
|
||||||
import androidx.compose.runtime.*
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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 org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
import bankmeister.composeapp.generated.resources.Res
|
private val typography = Typography(
|
||||||
import bankmeister.composeapp.generated.resources.compose_multiplatform
|
body1 = TextStyle(fontSize = 14.sp)
|
||||||
|
)
|
||||||
|
|
||||||
|
private val bankService = BankingService()
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
fun App() {
|
fun App() {
|
||||||
MaterialTheme {
|
val coroutineScope = rememberCoroutineScope()
|
||||||
var showContent by remember { mutableStateOf(false) }
|
val (transactions, setTransaction) = remember { mutableStateOf<List<AccountTransaction>>(emptyList()) }
|
||||||
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
|
|
||||||
Button(onClick = { showContent = !showContent }) {
|
coroutineScope.launch {
|
||||||
Text("Click me!")
|
setTransaction(bankService.getTransactions())
|
||||||
}
|
}
|
||||||
AnimatedVisibility(showContent) {
|
|
||||||
val greeting = remember { Greeting().greet() }
|
MaterialTheme(typography = typography) {
|
||||||
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(Modifier.fillMaxWidth().fillMaxHeight(), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
Image(painterResource(Res.drawable.compose_multiplatform), null)
|
TransactionsList(transactions)
|
||||||
Text("Compose: $greeting")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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"
|
kotlin = "2.0.10"
|
||||||
kotlinx-coroutines = "1.8.1"
|
kotlinx-coroutines = "1.8.1"
|
||||||
|
|
||||||
|
kcsv = "2.1.1"
|
||||||
|
klf = "1.5.1"
|
||||||
|
|
||||||
agp = "8.2.2"
|
agp = "8.2.2"
|
||||||
android-compileSdk = "34"
|
android-compileSdk = "34"
|
||||||
android-minSdk = "24"
|
android-minSdk = "24"
|
||||||
|
@ -19,6 +22,9 @@ compose-plugin = "1.6.11"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
|
|
||||||
[libraries]
|
[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-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-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" }
|
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidx-espresso-core" }
|
||||||
|
|
Loading…
Reference in New Issue