Implemented basic export data as CSV functionality (only CSV and only a few columns get exported)
This commit is contained in:
parent
e4c5e5ccfc
commit
84a147d4c9
|
@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.*
|
|||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.Send
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
|
@ -97,6 +98,18 @@ fun SideMenu(appContent: @Composable () -> Unit) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Divider(color = Colors.DrawerDivider)
|
||||
|
||||
Spacer(Modifier.height(12.dp))
|
||||
|
||||
NavigationMenuItem(itemModifier, "Daten exportieren", textColor, horizontalPadding = ItemHorizontalPadding, icon = { Icon(Icons.AutoMirrored.Filled.Send, "Konto hinzufügen", Modifier.size(iconSize)) }) {
|
||||
coroutineScope.launch {
|
||||
drawerState.close()
|
||||
}
|
||||
|
||||
uiState.showExportScreen.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
@ -5,11 +5,14 @@ import net.codinux.banking.ui.dialogs.AddAccountDialog
|
|||
import net.codinux.banking.ui.dialogs.ApplicationErrorDialog
|
||||
import net.codinux.banking.ui.dialogs.BankingClientErrorDialog
|
||||
import net.codinux.banking.ui.dialogs.EnterTanDialog
|
||||
import net.codinux.banking.ui.screens.ExportScreen
|
||||
import net.codinux.banking.ui.state.UiState
|
||||
|
||||
@Composable
|
||||
fun StateHandler(uiState: UiState) {
|
||||
val showAddAccountDialog by uiState.showAddAccountDialog.collectAsState()
|
||||
val showExportScreen by uiState.showExportScreen.collectAsState()
|
||||
|
||||
val tanChallengeReceived by uiState.tanChallengeReceived.collectAsState()
|
||||
val bankingClientError by uiState.bankingClientErrorOccurred.collectAsState()
|
||||
val applicationError by uiState.applicationErrorOccurred.collectAsState()
|
||||
|
@ -19,6 +22,11 @@ fun StateHandler(uiState: UiState) {
|
|||
AddAccountDialog { uiState.showAddAccountDialog.value = false }
|
||||
}
|
||||
|
||||
if (showExportScreen) {
|
||||
ExportScreen { uiState.showExportScreen.value = false }
|
||||
}
|
||||
|
||||
|
||||
tanChallengeReceived?.let { tanChallengeReceived ->
|
||||
EnterTanDialog(tanChallengeReceived) {
|
||||
uiState.tanChallengeReceived.value = null
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package net.codinux.banking.ui.composables.text
|
||||
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import net.codinux.banking.ui.config.Style
|
||||
|
||||
@Composable
|
||||
fun HeaderText(title: String, modifier: Modifier = Modifier, textAlign: TextAlign = TextAlign.Start) {
|
||||
Text(
|
||||
title,
|
||||
color = Style.HeaderTextColor,
|
||||
fontSize = Style.HeaderFontSize,
|
||||
fontWeight = Style.HeaderFontWeight,
|
||||
modifier = modifier,
|
||||
textAlign = textAlign
|
||||
)
|
||||
}
|
|
@ -12,6 +12,7 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import net.codinux.banking.ui.composables.text.HeaderText
|
||||
import net.codinux.banking.ui.config.Colors
|
||||
import net.codinux.banking.ui.config.Style
|
||||
import net.codinux.banking.ui.forms.*
|
||||
|
@ -34,13 +35,7 @@ fun BaseDialog(
|
|||
Column(Modifier.background(Color.White).padding(8.dp)) {
|
||||
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
title,
|
||||
color = Style.HeaderTextColor,
|
||||
fontSize = Style.HeaderFontSize,
|
||||
fontWeight = Style.HeaderFontWeight,
|
||||
modifier = Modifier.padding(top = 8.dp, bottom = 16.dp).weight(1f)
|
||||
)
|
||||
HeaderText(title, Modifier.padding(top = 8.dp, bottom = 16.dp).weight(1f))
|
||||
|
||||
TextButton(onDismiss, colors = ButtonDefaults.buttonColors(contentColor = Colors.Zinc700, backgroundColor = Color.Transparent)) {
|
||||
Icon(Icons.Filled.Close, contentDescription = "Close dialog", Modifier.size(32.dp))
|
||||
|
|
|
@ -8,6 +8,7 @@ import androidx.compose.material.TextButton
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import net.codinux.banking.ui.composables.text.HeaderText
|
||||
import net.codinux.banking.ui.config.Colors
|
||||
import net.codinux.banking.ui.config.Style
|
||||
|
||||
|
@ -22,14 +23,7 @@ fun ErrorDialog(
|
|||
AlertDialog(
|
||||
text = { Text(text) },
|
||||
title = { title?.let {
|
||||
Text(
|
||||
title,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textAlign = TextAlign.Center,
|
||||
color = Style.HeaderTextColor,
|
||||
fontSize = Style.HeaderFontSize,
|
||||
fontWeight = Style.HeaderFontWeight
|
||||
)
|
||||
HeaderText(title, Modifier.fillMaxWidth(), TextAlign.Center)
|
||||
} },
|
||||
onDismissRequest = { onDismiss?.invoke() },
|
||||
confirmButton = {
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
package net.codinux.banking.ui.screens
|
||||
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import net.codinux.banking.dataaccess.entities.AccountTransactionEntity
|
||||
import net.codinux.banking.ui.IOorDefault
|
||||
import net.codinux.banking.ui.config.Colors
|
||||
import net.codinux.banking.ui.config.DI
|
||||
import net.codinux.banking.ui.service.BankDataImporterAndExporter
|
||||
|
||||
@Composable
|
||||
fun ExportScreen(onClosed: () -> Unit) {
|
||||
var transactions: Collection<AccountTransactionEntity>
|
||||
|
||||
var exportedDataText by remember { mutableStateOf("") }
|
||||
|
||||
val importerExporter = BankDataImporterAndExporter()
|
||||
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
coroutineScope.launch(Dispatchers.IOorDefault) {
|
||||
transactions = DI.bankingService.getAllAccountTransactions() // a only very bit problematic: if in the meantime new transactions are retrieved, then this transactions property doesn't contain the newly retrieved transactions
|
||||
|
||||
exportedDataText = importerExporter.exportTransactionsAsCsv(transactions, ',')
|
||||
}
|
||||
|
||||
FullscreenViewBase("Umsätze exportieren", onClosed = onClosed) {
|
||||
Column {
|
||||
Text("Es gibt leider noch keinen \"Datei auswählen Dialog\", ist sehr schwierig plattformübergreifend umzusetzen, deshalb bitte folgenden Text kopieren und in eine Textdatei einfügen:")
|
||||
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
||||
TextButton({ clipboardManager.setText(AnnotatedString(exportedDataText))}) {
|
||||
Text("Kopieren", color = Colors.CodinuxSecondaryColor)
|
||||
}
|
||||
}
|
||||
|
||||
Column(Modifier.verticalScroll(ScrollState(0), enabled = true).horizontalScroll(ScrollState(0), enabled = true)) {
|
||||
SelectionContainer {
|
||||
Text(exportedDataText, fontFamily = FontFamily.Monospace)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package net.codinux.banking.ui.screens
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import net.codinux.banking.ui.composables.text.HeaderText
|
||||
import net.codinux.banking.ui.config.Colors
|
||||
|
||||
@Composable
|
||||
fun FullscreenViewBase(
|
||||
title: String,
|
||||
confirmButtonTitle: String = "OK",
|
||||
confirmButtonEnabled: Boolean = true,
|
||||
onClosed: () -> Unit,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
Column(Modifier.fillMaxSize().background(Color.White).padding(8.dp)) {
|
||||
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
HeaderText(title, Modifier.padding(top = 8.dp, bottom = 16.dp).weight(1f))
|
||||
|
||||
TextButton(onClosed, colors = ButtonDefaults.buttonColors(contentColor = Colors.Zinc700, backgroundColor = Color.Transparent)) {
|
||||
Icon(Icons.Filled.Close, contentDescription = "Close dialog", Modifier.size(32.dp))
|
||||
}
|
||||
}
|
||||
|
||||
Column(Modifier.fillMaxWidth().weight(1f)) {
|
||||
content()
|
||||
}
|
||||
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
||||
// TextButton(onClick = onClosed, Modifier.width(Style.DialogButtonWidth)) {
|
||||
// Text("Abbrechen", color = Colors.CodinuxSecondaryColor)
|
||||
// }
|
||||
//
|
||||
// Spacer(Modifier.width(8.dp))
|
||||
|
||||
TextButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = confirmButtonEnabled,
|
||||
onClick = { /* onConfirm?.invoke() ?: */ onClosed() }
|
||||
) {
|
||||
Text(confirmButtonTitle, color = Colors.CodinuxSecondaryColor, textAlign = TextAlign.Center)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package net.codinux.banking.ui.service
|
||||
|
||||
import net.codinux.banking.client.model.AccountTransaction
|
||||
import net.codinux.banking.client.model.Amount
|
||||
import net.codinux.csv.writer.CsvWriter
|
||||
|
||||
// TODO: extract to a common utility project
|
||||
class BankDataImporterAndExporter {
|
||||
|
||||
fun exportTransactionsAsCsv(transactions: Collection<AccountTransaction>, decimalSeparator: Char = '.'): String {
|
||||
val builder = StringBuilder()
|
||||
|
||||
val writer = CsvWriter.builder(if (decimalSeparator == ',') ';' else ',').writer(builder)
|
||||
writer.writeRow(
|
||||
"Betrug", "Währung", "Wertstellungstag", "Buchungstag",
|
||||
"Verwendungszweck", "Der Andere Name", "Der Andere Bank", "Der Andere Konto"
|
||||
)
|
||||
|
||||
transactions.forEach { transaction ->
|
||||
writer.writeRow(
|
||||
// TODO: add bank and bank account
|
||||
formatAmount(transaction.amount, decimalSeparator), transaction.currency,
|
||||
transaction.valueDate.toString(), transaction.bookingDate.toString(),
|
||||
transaction.sepaReference ?: transaction.reference,
|
||||
transaction.otherPartyName, transaction.otherPartyBankCode, transaction.otherPartyAccountId
|
||||
// TODO: export all columns / transaction data
|
||||
)
|
||||
}
|
||||
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
private fun formatAmount(amount: Amount, decimalSeparator: Char): String =
|
||||
if (decimalSeparator == '.') {
|
||||
amount.amount
|
||||
} else {
|
||||
amount.amount.replace('.', decimalSeparator)
|
||||
}
|
||||
|
||||
}
|
|
@ -42,14 +42,22 @@ class BankingService(
|
|||
|
||||
suspend fun init() {
|
||||
try {
|
||||
uiState.userAccounts.value = bankingRepository.getAllUserAccounts()
|
||||
uiState.userAccounts.value = getAllUserAccounts()
|
||||
|
||||
uiState.transactions.value = bankingRepository.getAllAccountTransactionsAsViewModel()
|
||||
uiState.transactions.value = getAllAccountTransactionsAsViewModel()
|
||||
} catch (e: Throwable) {
|
||||
log.error(e) { "Could not read all user accounts and account transactions from repository" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun getAllUserAccounts() = bankingRepository.getAllUserAccounts()
|
||||
|
||||
fun getAllAccountTransactions() = bankingRepository.getAllAccountTransactions()
|
||||
|
||||
fun getAllAccountTransactionsAsViewModel() = bankingRepository.getAllAccountTransactionsAsViewModel()
|
||||
|
||||
|
||||
suspend fun findBanks(query: String): List<BankInfo> =
|
||||
bankFinder.findBankByNameBankCodeOrCity(query, 25)
|
||||
|
||||
|
|
|
@ -28,6 +28,8 @@ class UiState : ViewModel() {
|
|||
|
||||
val showAddAccountDialog = MutableStateFlow(false)
|
||||
|
||||
val showExportScreen = MutableStateFlow(false)
|
||||
|
||||
val tanChallengeReceived = MutableStateFlow<TanChallengeReceived?>(null)
|
||||
|
||||
val bankingClientErrorOccurred = MutableStateFlow<BankingClientError?>(null)
|
||||
|
|
Loading…
Reference in New Issue