Showing errors returned from Banking Client

This commit is contained in:
dankito 2024-08-26 18:08:48 +02:00
parent ee4287ada1
commit 9d232dfb43
12 changed files with 190 additions and 21 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,6 @@
package net.codinux.banking.ui.model
enum class BankingClientAction {
AddAccount,
UpdateAccountTransactions
}

View File

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

View File

@ -0,0 +1,7 @@
package net.codinux.banking.ui.model
object Config {
const val NewLine = "\r\n"
}

View File

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

View File

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

View File

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

View File

@ -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<List<AccountTransaction>>(emptyList())
val bankingClientErrorOccurred = MutableStateFlow<BankingClientError?>(null)
}