From 9d232dfb433130b76cb1fe722c91951b5fb9487a Mon Sep 17 00:00:00 2001 From: dankito Date: Mon, 26 Aug 2024 18:08:48 +0200 Subject: [PATCH] Showing errors returned from Banking Client --- .../kotlin/net/codinux/banking/ui/App.kt | 3 ++ .../banking/ui/composables/StateHandler.kt | 19 ++++++++ .../banking/ui/dialogs/AddAccountDialog.kt | 19 ++++---- .../ui/dialogs/BankingClientErrorDialog.kt | 25 +++++++++++ .../codinux/banking/ui/dialogs/ErrorDialog.kt | 42 ++++++++++++++++++ .../banking/ui/model/BankingClientAction.kt | 6 +++ .../banking/ui/model/BankingClientError.kt | 10 +++++ .../net/codinux/banking/ui/model/Config.kt | 7 +++ .../banking/ui/service/BankingService.kt | 43 +++++++++++++++---- .../net/codinux/banking/ui/service/DI.kt | 5 ++- .../net/codinux/banking/ui/service/Style.kt | 18 ++++++++ .../net/codinux/banking/ui/state/UiState.kt | 14 ++++++ 12 files changed, 190 insertions(+), 21 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/StateHandler.kt create mode 100644 composeApp/src/commonMain/kotlin/net/codinux/banking/ui/dialogs/BankingClientErrorDialog.kt create mode 100644 composeApp/src/commonMain/kotlin/net/codinux/banking/ui/dialogs/ErrorDialog.kt create mode 100644 composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/BankingClientAction.kt create mode 100644 composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/BankingClientError.kt create mode 100644 composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/Config.kt create mode 100644 composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/Style.kt create mode 100644 composeApp/src/commonMain/kotlin/net/codinux/banking/ui/state/UiState.kt diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/App.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/App.kt index f072e1a..c43d2dd 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/App.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/App.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import kotlinx.coroutines.launch import net.codinux.banking.client.model.AccountTransaction +import net.codinux.banking.ui.composables.StateHandler import net.codinux.banking.ui.composables.TransactionsList import net.codinux.banking.ui.dialogs.AddAccountDialog import net.codinux.banking.ui.service.Colors @@ -65,5 +66,7 @@ fun App() { AddAccountDialog { showAddAccountDialog = false } } } + + StateHandler(DI.uiState) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/StateHandler.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/StateHandler.kt new file mode 100644 index 0000000..8b9527c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/StateHandler.kt @@ -0,0 +1,19 @@ +package net.codinux.banking.ui.composables + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import net.codinux.banking.ui.dialogs.BankingClientErrorDialog +import net.codinux.banking.ui.state.UiState + +@Composable +fun StateHandler(uiState: UiState) { + val bankingClientError by uiState.bankingClientErrorOccurred.collectAsState() + + bankingClientError?.let { + BankingClientErrorDialog(it) { + uiState.bankingClientErrorOccurred.value = null + } + } + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/dialogs/AddAccountDialog.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/dialogs/AddAccountDialog.kt index 4f4de4e..6e51cd8 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/dialogs/AddAccountDialog.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/dialogs/AddAccountDialog.kt @@ -9,9 +9,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -20,6 +18,7 @@ import net.codinux.banking.ui.forms.* import net.codinux.banking.ui.model.BankInfo import net.codinux.banking.ui.service.Colors import net.codinux.banking.ui.service.DI +import net.codinux.banking.ui.service.Style private val bankingService = DI.bankingService @@ -48,9 +47,9 @@ fun AddAccountDialog( Row(Modifier.fillMaxWidth()) { Text( "Bank Konto hinzufügen", - color = Color.Black, - fontSize = 20.sp, - fontWeight = FontWeight.Bold, + color = Style.HeaderTextColor, + fontSize = Style.HeaderFontSize, + fontWeight = Style.HeaderFontWeight, modifier = Modifier.padding(top = 8.dp, bottom = 16.dp).weight(1f) ) @@ -95,29 +94,27 @@ fun AddAccountDialog( Spacer(modifier = Modifier.height(16.dp)) Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { - TextButton(onClick = onDismiss) { + TextButton(onClick = onDismiss, Modifier.width(Style.DialogButtonWidth)) { Text("Abbrechen") } Spacer(Modifier.width(8.dp)) TextButton( - modifier = Modifier.width(150.dp), + modifier = Modifier.width(Style.DialogButtonWidth), enabled = isRequiredDataEntered && isAddingAccount == false, onClick = { selectedBank?.let { isAddingAccount = true coroutineScope.launch { // TODO: launch on Dispatchers.IO where it is available - val errorMessage = DI.bankingService.addAccount(selectedBank!!, loginName, password) + val successful = DI.bankingService.addAccount(selectedBank!!, loginName, password) withContext(Dispatchers.Main) { isAddingAccount = false - if (errorMessage == null) { + if (successful) { onDismiss() - } else { - // TODO: show error Message } } } diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/dialogs/BankingClientErrorDialog.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/dialogs/BankingClientErrorDialog.kt new file mode 100644 index 0000000..db5d72d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/dialogs/BankingClientErrorDialog.kt @@ -0,0 +1,25 @@ +package net.codinux.banking.ui.dialogs + +import androidx.compose.runtime.Composable +import net.codinux.banking.ui.model.BankingClientAction +import net.codinux.banking.ui.model.BankingClientError +import net.codinux.banking.ui.model.Config + +@Composable +fun BankingClientErrorDialog(error: BankingClientError, onDismiss: (() -> Unit)? = null) { + val title = when (error.erroneousAction) { + BankingClientAction.AddAccount -> "Konto konnte nicht hinzugefügt werden" + BankingClientAction.UpdateAccountTransactions -> "Umsätze konnten nicht aktualisiert werden" + } + + val text = if (error.error.internalError != null) { + error.error.internalError!! + } else if (error.error.errorMessagesFromBank.isNotEmpty()) { + "Fehlermeldung Ihrer Bank:" + Config.NewLine + Config.NewLine + error.error.errorMessagesFromBank.joinToString(Config.NewLine) + } else { // should actually never occur + "${error.error.type}" // TODO: add translations for different error types + } + + + ErrorDialog(text, title, onDismiss = onDismiss) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/dialogs/ErrorDialog.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/dialogs/ErrorDialog.kt new file mode 100644 index 0000000..ed5bb5f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/dialogs/ErrorDialog.kt @@ -0,0 +1,42 @@ +package net.codinux.banking.ui.dialogs + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.width +import androidx.compose.material.AlertDialog +import androidx.compose.material.Text +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.service.Colors +import net.codinux.banking.ui.service.Style + +@Composable +fun ErrorDialog( + text: String, + title: String? = null, + confirmButtonText: String = "OK", + onDismiss: (() -> Unit)? = null +) { + + AlertDialog( + text = { Text(text) }, + title = { title?.let { + Text( + title, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + color = Style.HeaderTextColor, + fontSize = Style.HeaderFontSize, + fontWeight = Style.HeaderFontWeight + ) + } }, + onDismissRequest = { onDismiss?.invoke() }, + confirmButton = { + TextButton({ onDismiss?.invoke() }, Modifier.width(Style.DialogButtonWidth)) { + Text(confirmButtonText, color = Colors.CodinuxSecondaryColor) + } + } + ) + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/BankingClientAction.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/BankingClientAction.kt new file mode 100644 index 0000000..8ff5d95 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/BankingClientAction.kt @@ -0,0 +1,6 @@ +package net.codinux.banking.ui.model + +enum class BankingClientAction { + AddAccount, + UpdateAccountTransactions +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/BankingClientError.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/BankingClientError.kt new file mode 100644 index 0000000..c0dc745 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/BankingClientError.kt @@ -0,0 +1,10 @@ +package net.codinux.banking.ui.model + +import net.codinux.banking.client.model.response.Error + +data class BankingClientError( + val erroneousAction: BankingClientAction, + val error: Error +) { + override fun toString() = "$erroneousAction $error" +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/Config.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/Config.kt new file mode 100644 index 0000000..8398c90 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/Config.kt @@ -0,0 +1,7 @@ +package net.codinux.banking.ui.model + +object Config { + + const val NewLine = "\r\n" + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/BankingService.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/BankingService.kt index 2ce5264..05b1939 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/BankingService.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/BankingService.kt @@ -6,15 +6,23 @@ import net.codinux.banking.client.SimpleBankingClientCallback import net.codinux.banking.client.fints4k.FinTs4kBankingClientForCustomer import net.codinux.banking.client.model.AccountTransaction import net.codinux.banking.client.model.Amount +import net.codinux.banking.client.model.response.GetAccountDataResponse +import net.codinux.banking.client.model.response.Response +import net.codinux.banking.client.model.response.ResponseType import net.codinux.banking.fints.config.FinTsClientConfiguration import net.codinux.banking.fints.config.FinTsClientOptions import net.codinux.banking.ui.model.BankInfo +import net.codinux.banking.ui.model.BankingClientAction +import net.codinux.banking.ui.model.BankingClientError +import net.codinux.banking.ui.state.UiState import net.codinux.csv.reader.CsvReader +import net.codinux.log.Log import net.codinux.log.logger import org.jetbrains.compose.resources.ExperimentalResourceApi @OptIn(ExperimentalResourceApi::class) class BankingService( + private val uiState: UiState, private val bankFinder: BankFinder ) { @@ -37,7 +45,7 @@ class BankingService( return transactions } - suspend fun addAccount(bank: BankInfo, loginName: String, password: String): String? { + suspend fun addAccount(bank: BankInfo, loginName: String, password: String): Boolean { try { val config = FinTsClientConfiguration(FinTsClientOptions(true)) val client = FinTs4kBankingClientForCustomer(bank.bankCode, loginName, password, config, SimpleBankingClientCallback { tanChallenge, callback -> @@ -45,18 +53,35 @@ class BankingService( }) val response = client.getAccountDataAsync() - response.data?.let { accountData -> - if (cachedTransactions == null) { - cachedTransactions = accountData.bookedTransactions - } else { - cachedTransactions = (cachedTransactions!! + accountData.bookedTransactions).sortedByDescending { it.valueDate } - } + + if (response.type == ResponseType.Success && response.data != null) { + handleSuccessfulGetAccountDataResponse(response.data!!) + } else { + handleUnsuccessfulBankingClientResponse(BankingClientAction.AddAccount, response) } - return response.error?.internalError ?: response.error?.errorMessagesFromBank?.joinToString("\n") + return response.type == ResponseType.Success } catch (e: Throwable) { log.error(e) { "Could not add account for ${bank.name} $loginName" } - return e.message + + // TODO: handle internal error + + return false + } + } + + private fun handleSuccessfulGetAccountDataResponse(response: GetAccountDataResponse) { + // TODO: save customer + if (cachedTransactions == null) { + cachedTransactions = response.bookedTransactions + } else { + cachedTransactions = (cachedTransactions!! + response.bookedTransactions).sortedByDescending { it.valueDate } + } + } + + private fun handleUnsuccessfulBankingClientResponse(action: BankingClientAction, response: Response<*>) { + response.error?.let { error -> + uiState.bankingClientErrorOccurred.value = BankingClientError(action, error) } } diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/DI.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/DI.kt index a626afe..ba54628 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/DI.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/DI.kt @@ -2,14 +2,17 @@ package net.codinux.banking.ui.service import net.codinux.banking.ui.Platform import net.codinux.banking.ui.getPlatform +import net.codinux.banking.ui.state.UiState object DI { + val uiState = UiState() + val platform: Platform = getPlatform() val bankFinder = BankFinder() - val bankingService = BankingService(bankFinder) + val bankingService = BankingService(uiState, bankFinder) val formatUtil = FormatUtil() diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/Style.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/Style.kt new file mode 100644 index 0000000..962591d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/Style.kt @@ -0,0 +1,18 @@ +package net.codinux.banking.ui.service + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +object Style { + + val HeaderFontSize = 20.sp + + val HeaderTextColor: Color = Color.Black // TODO: find a better one like a dark gray + + val HeaderFontWeight: FontWeight = FontWeight.Bold + + val DialogButtonWidth = 200.dp + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/state/UiState.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/state/UiState.kt new file mode 100644 index 0000000..c6baf3c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/state/UiState.kt @@ -0,0 +1,14 @@ +package net.codinux.banking.ui.state + +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import net.codinux.banking.client.model.AccountTransaction +import net.codinux.banking.ui.model.BankingClientError + +class UiState : ViewModel() { + + val transactions = MutableStateFlow>(emptyList()) + + val bankingClientErrorOccurred = MutableStateFlow(null) + +} \ No newline at end of file