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.foundation.verticalScroll
|
||||||
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.Send
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
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.ApplicationErrorDialog
|
||||||
import net.codinux.banking.ui.dialogs.BankingClientErrorDialog
|
import net.codinux.banking.ui.dialogs.BankingClientErrorDialog
|
||||||
import net.codinux.banking.ui.dialogs.EnterTanDialog
|
import net.codinux.banking.ui.dialogs.EnterTanDialog
|
||||||
|
import net.codinux.banking.ui.screens.ExportScreen
|
||||||
import net.codinux.banking.ui.state.UiState
|
import net.codinux.banking.ui.state.UiState
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun StateHandler(uiState: UiState) {
|
fun StateHandler(uiState: UiState) {
|
||||||
val showAddAccountDialog by uiState.showAddAccountDialog.collectAsState()
|
val showAddAccountDialog by uiState.showAddAccountDialog.collectAsState()
|
||||||
|
val showExportScreen by uiState.showExportScreen.collectAsState()
|
||||||
|
|
||||||
val tanChallengeReceived by uiState.tanChallengeReceived.collectAsState()
|
val tanChallengeReceived by uiState.tanChallengeReceived.collectAsState()
|
||||||
val bankingClientError by uiState.bankingClientErrorOccurred.collectAsState()
|
val bankingClientError by uiState.bankingClientErrorOccurred.collectAsState()
|
||||||
val applicationError by uiState.applicationErrorOccurred.collectAsState()
|
val applicationError by uiState.applicationErrorOccurred.collectAsState()
|
||||||
|
@ -19,6 +22,11 @@ fun StateHandler(uiState: UiState) {
|
||||||
AddAccountDialog { uiState.showAddAccountDialog.value = false }
|
AddAccountDialog { uiState.showAddAccountDialog.value = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showExportScreen) {
|
||||||
|
ExportScreen { uiState.showExportScreen.value = false }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
tanChallengeReceived?.let { tanChallengeReceived ->
|
tanChallengeReceived?.let { tanChallengeReceived ->
|
||||||
EnterTanDialog(tanChallengeReceived) {
|
EnterTanDialog(tanChallengeReceived) {
|
||||||
uiState.tanChallengeReceived.value = null
|
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.unit.dp
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
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.Colors
|
||||||
import net.codinux.banking.ui.config.Style
|
import net.codinux.banking.ui.config.Style
|
||||||
import net.codinux.banking.ui.forms.*
|
import net.codinux.banking.ui.forms.*
|
||||||
|
@ -34,13 +35,7 @@ fun BaseDialog(
|
||||||
Column(Modifier.background(Color.White).padding(8.dp)) {
|
Column(Modifier.background(Color.White).padding(8.dp)) {
|
||||||
|
|
||||||
Row(Modifier.fillMaxWidth()) {
|
Row(Modifier.fillMaxWidth()) {
|
||||||
Text(
|
HeaderText(title, Modifier.padding(top = 8.dp, bottom = 16.dp).weight(1f))
|
||||||
title,
|
|
||||||
color = Style.HeaderTextColor,
|
|
||||||
fontSize = Style.HeaderFontSize,
|
|
||||||
fontWeight = Style.HeaderFontWeight,
|
|
||||||
modifier = Modifier.padding(top = 8.dp, bottom = 16.dp).weight(1f)
|
|
||||||
)
|
|
||||||
|
|
||||||
TextButton(onDismiss, colors = ButtonDefaults.buttonColors(contentColor = Colors.Zinc700, backgroundColor = Color.Transparent)) {
|
TextButton(onDismiss, colors = ButtonDefaults.buttonColors(contentColor = Colors.Zinc700, backgroundColor = Color.Transparent)) {
|
||||||
Icon(Icons.Filled.Close, contentDescription = "Close dialog", Modifier.size(32.dp))
|
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.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
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.Colors
|
||||||
import net.codinux.banking.ui.config.Style
|
import net.codinux.banking.ui.config.Style
|
||||||
|
|
||||||
|
@ -22,14 +23,7 @@ fun ErrorDialog(
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
text = { Text(text) },
|
text = { Text(text) },
|
||||||
title = { title?.let {
|
title = { title?.let {
|
||||||
Text(
|
HeaderText(title, Modifier.fillMaxWidth(), TextAlign.Center)
|
||||||
title,
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
color = Style.HeaderTextColor,
|
|
||||||
fontSize = Style.HeaderFontSize,
|
|
||||||
fontWeight = Style.HeaderFontWeight
|
|
||||||
)
|
|
||||||
} },
|
} },
|
||||||
onDismissRequest = { onDismiss?.invoke() },
|
onDismissRequest = { onDismiss?.invoke() },
|
||||||
confirmButton = {
|
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() {
|
suspend fun init() {
|
||||||
try {
|
try {
|
||||||
uiState.userAccounts.value = bankingRepository.getAllUserAccounts()
|
uiState.userAccounts.value = getAllUserAccounts()
|
||||||
|
|
||||||
uiState.transactions.value = bankingRepository.getAllAccountTransactionsAsViewModel()
|
uiState.transactions.value = getAllAccountTransactionsAsViewModel()
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
log.error(e) { "Could not read all user accounts and account transactions from repository" }
|
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> =
|
suspend fun findBanks(query: String): List<BankInfo> =
|
||||||
bankFinder.findBankByNameBankCodeOrCity(query, 25)
|
bankFinder.findBankByNameBankCodeOrCity(query, 25)
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,8 @@ class UiState : ViewModel() {
|
||||||
|
|
||||||
val showAddAccountDialog = MutableStateFlow(false)
|
val showAddAccountDialog = MutableStateFlow(false)
|
||||||
|
|
||||||
|
val showExportScreen = MutableStateFlow(false)
|
||||||
|
|
||||||
val tanChallengeReceived = MutableStateFlow<TanChallengeReceived?>(null)
|
val tanChallengeReceived = MutableStateFlow<TanChallengeReceived?>(null)
|
||||||
|
|
||||||
val bankingClientErrorOccurred = MutableStateFlow<BankingClientError?>(null)
|
val bankingClientErrorOccurred = MutableStateFlow<BankingClientError?>(null)
|
||||||
|
|
Loading…
Reference in New Issue