Implemented TransferMoneyDialog
This commit is contained in:
parent
1368ece023
commit
dde54b75d3
|
@ -5,16 +5,16 @@ import androidx.compose.material.SnackbarHostState
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import net.codinux.banking.ui.config.DI
|
import net.codinux.banking.ui.config.DI
|
||||||
import net.codinux.banking.ui.dialogs.AddAccountDialog
|
import net.codinux.banking.ui.dialogs.*
|
||||||
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.screens.ExportScreen
|
||||||
import net.codinux.banking.ui.state.UiState
|
import net.codinux.banking.ui.state.UiState
|
||||||
|
|
||||||
|
private val formatUtil = DI.formatUtil
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun StateHandler(uiState: UiState, snackbarHostState: SnackbarHostState) {
|
fun StateHandler(uiState: UiState, snackbarHostState: SnackbarHostState) {
|
||||||
val showAddAccountDialog by uiState.showAddAccountDialog.collectAsState()
|
val showAddAccountDialog by uiState.showAddAccountDialog.collectAsState()
|
||||||
|
val showTransferMoneyDialog by uiState.showTransferMoneyDialog.collectAsState()
|
||||||
val showExportScreen by uiState.showExportScreen.collectAsState()
|
val showExportScreen by uiState.showExportScreen.collectAsState()
|
||||||
|
|
||||||
val tanChallengeReceived by uiState.tanChallengeReceived.collectAsState()
|
val tanChallengeReceived by uiState.tanChallengeReceived.collectAsState()
|
||||||
|
@ -28,6 +28,10 @@ fun StateHandler(uiState: UiState, snackbarHostState: SnackbarHostState) {
|
||||||
AddAccountDialog { uiState.showAddAccountDialog.value = false }
|
AddAccountDialog { uiState.showAddAccountDialog.value = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showTransferMoneyDialog) {
|
||||||
|
TransferMoneyDialog { uiState.showTransferMoneyDialog.value = false }
|
||||||
|
}
|
||||||
|
|
||||||
if (showExportScreen) {
|
if (showExportScreen) {
|
||||||
ExportScreen { uiState.showExportScreen.value = false }
|
ExportScreen { uiState.showExportScreen.value = false }
|
||||||
}
|
}
|
||||||
|
@ -54,7 +58,7 @@ fun StateHandler(uiState: UiState, snackbarHostState: SnackbarHostState) {
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
DI.uiState.transactionsRetrievedEvents.collect { event ->
|
uiState.transactionsRetrievedEvents.collect { event ->
|
||||||
val messagePrefix = if (event.newTransactions.isEmpty()) {
|
val messagePrefix = if (event.newTransactions.isEmpty()) {
|
||||||
"Keine neuen Umsätze"
|
"Keine neuen Umsätze"
|
||||||
} else if (event.newTransactions.size == 1) {
|
} else if (event.newTransactions.size == 1) {
|
||||||
|
@ -70,6 +74,16 @@ fun StateHandler(uiState: UiState, snackbarHostState: SnackbarHostState) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
coroutineScope.launch {
|
||||||
|
uiState.transferredMoneyEvents.collect { event ->
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = "${formatUtil.formatAmount(event.amount, event.currency)} wurden erfolgreich an ${event.recipientName} überwiesen",
|
||||||
|
actionLabel = "Das freut mich",
|
||||||
|
duration = SnackbarDuration.Long
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -8,6 +8,8 @@ object Internationalization {
|
||||||
|
|
||||||
const val ErrorUpdateAccountTransactions = "Umsätze konnten nicht aktualisiert werden"
|
const val ErrorUpdateAccountTransactions = "Umsätze konnten nicht aktualisiert werden"
|
||||||
|
|
||||||
|
const val ErrorTransferMoney = "Überweisung konnte nicht ausgeführt werden"
|
||||||
|
|
||||||
|
|
||||||
fun getTextForActionRequiringTan(action: ActionRequiringTan): String = when (action) {
|
fun getTextForActionRequiringTan(action: ActionRequiringTan): String = when (action) {
|
||||||
ActionRequiringTan.GetAnonymousBankInfo,
|
ActionRequiringTan.GetAnonymousBankInfo,
|
||||||
|
|
|
@ -10,6 +10,7 @@ fun ApplicationErrorDialog(error: ApplicationError, onDismiss: (() -> Unit)? = n
|
||||||
val title = when (error.erroneousAction) {
|
val title = when (error.erroneousAction) {
|
||||||
ErroneousAction.AddAccount -> Internationalization.ErrorAddAccount
|
ErroneousAction.AddAccount -> Internationalization.ErrorAddAccount
|
||||||
ErroneousAction.UpdateAccountTransactions -> Internationalization.ErrorUpdateAccountTransactions
|
ErroneousAction.UpdateAccountTransactions -> Internationalization.ErrorUpdateAccountTransactions
|
||||||
|
ErroneousAction.TransferMoney -> Internationalization.ErrorTransferMoney
|
||||||
}
|
}
|
||||||
|
|
||||||
// add exception stacktrace?
|
// add exception stacktrace?
|
||||||
|
|
|
@ -11,6 +11,7 @@ fun BankingClientErrorDialog(error: BankingClientError, onDismiss: (() -> Unit)?
|
||||||
val title = when (error.erroneousAction) {
|
val title = when (error.erroneousAction) {
|
||||||
BankingClientAction.AddAccount -> Internationalization.ErrorAddAccount
|
BankingClientAction.AddAccount -> Internationalization.ErrorAddAccount
|
||||||
BankingClientAction.UpdateAccountTransactions -> Internationalization.ErrorUpdateAccountTransactions
|
BankingClientAction.UpdateAccountTransactions -> Internationalization.ErrorUpdateAccountTransactions
|
||||||
|
BankingClientAction.TransferMoney -> Internationalization.ErrorTransferMoney
|
||||||
}
|
}
|
||||||
|
|
||||||
val text = if (error.error.internalError != null) {
|
val text = if (error.error.internalError != null) {
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
package net.codinux.banking.ui.dialogs
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import net.codinux.banking.client.model.Amount
|
||||||
|
import net.codinux.banking.client.model.BankAccountFeatures
|
||||||
|
import net.codinux.banking.ui.IOorDefault
|
||||||
|
import net.codinux.banking.ui.composables.BankIcon
|
||||||
|
import net.codinux.banking.ui.config.Colors
|
||||||
|
import net.codinux.banking.ui.config.DI
|
||||||
|
import net.codinux.banking.ui.forms.OutlinedTextField
|
||||||
|
import net.codinux.banking.ui.forms.Select
|
||||||
|
import net.codinux.banking.ui.model.error.ErroneousAction
|
||||||
|
|
||||||
|
private val uiState = DI.uiState
|
||||||
|
|
||||||
|
private val bankingService = DI.bankingService
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TransferMoneyDialog(
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
) {
|
||||||
|
val userAccounts = uiState.userAccounts.value
|
||||||
|
val accountsToUserAccount = userAccounts.sortedBy { it.displayIndex }
|
||||||
|
.flatMap { user -> user.accounts.sortedBy { it.displayIndex }.map { it to user } }.toMap()
|
||||||
|
|
||||||
|
val accountsSupportingTransferringMoney = userAccounts.flatMap { it.accounts }
|
||||||
|
.filter { it.supportsAnyFeature(BankAccountFeatures.TransferMoney, BankAccountFeatures.InstantPayment) }
|
||||||
|
|
||||||
|
if (accountsSupportingTransferringMoney.isEmpty()) {
|
||||||
|
uiState.applicationErrorOccurred(ErroneousAction.TransferMoney, "Keines Ihrer Konten unterstützt das Überweisen von Geld")
|
||||||
|
onDismiss()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var senderAccount by remember { mutableStateOf(accountsSupportingTransferringMoney.first()) }
|
||||||
|
|
||||||
|
var recipientName by remember { mutableStateOf("") }
|
||||||
|
var recipientAccountIdentifier by remember { mutableStateOf("") }
|
||||||
|
var amount by remember { mutableStateOf("") }
|
||||||
|
var paymentReference by remember { mutableStateOf("") }
|
||||||
|
val accountSupportsInstantTransfer by remember(senderAccount) { derivedStateOf { senderAccount.supportsAnyFeature(BankAccountFeatures.InstantPayment) } }
|
||||||
|
var instantTransfer by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val isRequiredDataEntered by remember(recipientName, recipientAccountIdentifier, amount) {
|
||||||
|
// TODO: add check if it's a valid IBAN
|
||||||
|
derivedStateOf { recipientName.length > 2 && recipientAccountIdentifier.length >= 18 && amount.isNotBlank() }
|
||||||
|
}
|
||||||
|
|
||||||
|
var isTransferringMoney by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val verticalSpace = 8.dp
|
||||||
|
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
|
||||||
|
fun confirmCalled() {
|
||||||
|
isTransferringMoney = true
|
||||||
|
|
||||||
|
coroutineScope.launch(Dispatchers.IOorDefault) {
|
||||||
|
val successful = bankingService.transferMoney(
|
||||||
|
accountsToUserAccount[senderAccount]!!, senderAccount,
|
||||||
|
recipientName, recipientAccountIdentifier,
|
||||||
|
Amount(amount), "EUR", // TODO: add input field for currency
|
||||||
|
paymentReference, instantTransfer && accountSupportsInstantTransfer
|
||||||
|
// TODO: determine BIC to IBAN
|
||||||
|
)
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
isTransferringMoney = false
|
||||||
|
|
||||||
|
if (successful) {
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BaseDialog(
|
||||||
|
title = "Neue Überweisung ...",
|
||||||
|
confirmButtonTitle = "Überweisen",
|
||||||
|
confirmButtonEnabled = isRequiredDataEntered && isTransferringMoney == false,
|
||||||
|
showProgressIndicatorOnConfirmButton = isTransferringMoney,
|
||||||
|
useMoreThanPlatformDefaultWidthOnMobile = true,
|
||||||
|
onDismiss = onDismiss,
|
||||||
|
onConfirm = { confirmCalled() }
|
||||||
|
) {
|
||||||
|
Select(
|
||||||
|
"Konto", "Alle Konten anzeigen",
|
||||||
|
accountsSupportingTransferringMoney, senderAccount, { senderAccount = it },
|
||||||
|
{ account -> "${accountsToUserAccount[account]?.displayName} ${account.displayName}" },
|
||||||
|
{ BankIcon(accountsToUserAccount[senderAccount]) }
|
||||||
|
) { account ->
|
||||||
|
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
BankIcon(accountsToUserAccount[account], Modifier.padding(end = 6.dp))
|
||||||
|
|
||||||
|
Text("${accountsToUserAccount[account]?.displayName} ${account.displayName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(Modifier.padding(top = verticalSpace)) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = recipientName,
|
||||||
|
onValueChange = { recipientName = it },
|
||||||
|
label = { Text("Name des Empfängers / der Empfängerin") },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(verticalSpace))
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = recipientAccountIdentifier,
|
||||||
|
onValueChange = { recipientAccountIdentifier = it },
|
||||||
|
label = { Text("IBAN") },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(verticalSpace))
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = amount,
|
||||||
|
onValueChange = { amount = it },
|
||||||
|
label = { Text("Betrag") },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal, imeAction = ImeAction.Next)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(verticalSpace))
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = paymentReference,
|
||||||
|
onValueChange = { paymentReference = it },
|
||||||
|
label = { Text("Verwendungszweck") },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(Modifier.padding(top = verticalSpace), verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Switch(
|
||||||
|
checked = instantTransfer,
|
||||||
|
onCheckedChange = { instantTransfer = it },
|
||||||
|
enabled = accountSupportsInstantTransfer,
|
||||||
|
modifier = Modifier.padding(end = 4.dp),
|
||||||
|
colors = SwitchDefaults.colors(checkedThumbColor = Colors.CodinuxSecondaryColor)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text("Echtzeitüberweisung (evtl. kostenpflichtig)", Modifier.clickable { instantTransfer = !instantTransfer })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package net.codinux.banking.ui.forms
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.material.*
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||||
|
import androidx.compose.material.icons.filled.ArrowDropUp
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
|
@Composable
|
||||||
|
fun <T> Select(
|
||||||
|
label: String,
|
||||||
|
contentDescription: String,
|
||||||
|
items: Collection<T>,
|
||||||
|
selectedItem: T,
|
||||||
|
onSelectedItemChanged: (T) -> Unit,
|
||||||
|
getItemDisplayText: (T) -> String,
|
||||||
|
leadingIcon: @Composable (() -> Unit)? = null,
|
||||||
|
dropDownItemContent: @Composable ((T) -> Unit)? = null
|
||||||
|
) {
|
||||||
|
var showDropDownMenu by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
ExposedDropdownMenuBox(showDropDownMenu, { isExpanded -> showDropDownMenu = isExpanded }, Modifier.fillMaxWidth()) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = getItemDisplayText(selectedItem),
|
||||||
|
onValueChange = { },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
label = { Text(label) },
|
||||||
|
readOnly = true,
|
||||||
|
maxLines = 1,
|
||||||
|
trailingIcon = {
|
||||||
|
if (showDropDownMenu) {
|
||||||
|
Icon(Icons.Filled.ArrowDropUp, contentDescription)
|
||||||
|
} else {
|
||||||
|
Icon(Icons.Filled.ArrowDropDown, contentDescription)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
leadingIcon = leadingIcon
|
||||||
|
)
|
||||||
|
|
||||||
|
// due to a bug (still not fixed since 2021) in ExposedDropdownMenu its popup has a maximum width of 800 pixel / 320dp which is too less to fit
|
||||||
|
// TextField's width, see https://issuetracker.google.com/issues/205589613
|
||||||
|
DropdownMenu(showDropDownMenu, { showDropDownMenu = false }, Modifier.exposedDropdownSize(true)) {
|
||||||
|
items.forEach { item ->
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = {
|
||||||
|
showDropDownMenu = false
|
||||||
|
onSelectedItemChanged(item)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
dropDownItemContent?.invoke(item) ?: Text(getItemDisplayText(item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,5 +2,8 @@ package net.codinux.banking.ui.model.error
|
||||||
|
|
||||||
enum class BankingClientAction {
|
enum class BankingClientAction {
|
||||||
AddAccount,
|
AddAccount,
|
||||||
UpdateAccountTransactions
|
|
||||||
|
UpdateAccountTransactions,
|
||||||
|
|
||||||
|
TransferMoney
|
||||||
}
|
}
|
|
@ -2,5 +2,8 @@ package net.codinux.banking.ui.model.error
|
||||||
|
|
||||||
enum class ErroneousAction {
|
enum class ErroneousAction {
|
||||||
AddAccount,
|
AddAccount,
|
||||||
UpdateAccountTransactions
|
|
||||||
|
UpdateAccountTransactions,
|
||||||
|
|
||||||
|
TransferMoney
|
||||||
}
|
}
|
|
@ -4,7 +4,7 @@ import net.codinux.banking.client.model.BankAccount
|
||||||
import net.codinux.banking.client.model.UserAccount
|
import net.codinux.banking.client.model.UserAccount
|
||||||
import net.codinux.banking.ui.model.AccountTransactionViewModel
|
import net.codinux.banking.ui.model.AccountTransactionViewModel
|
||||||
|
|
||||||
data class AccountTransactionsRetrieved(
|
data class AccountTransactionsRetrievedEvent(
|
||||||
val user: UserAccount,
|
val user: UserAccount,
|
||||||
val account: BankAccount,
|
val account: BankAccount,
|
||||||
val newTransactions: List<AccountTransactionViewModel>
|
val newTransactions: List<AccountTransactionViewModel>
|
|
@ -0,0 +1,9 @@
|
||||||
|
package net.codinux.banking.ui.model.events
|
||||||
|
|
||||||
|
import net.codinux.banking.client.model.Amount
|
||||||
|
|
||||||
|
data class TransferredMoneyEvent(
|
||||||
|
val recipientName: String,
|
||||||
|
val amount: Amount,
|
||||||
|
val currency: String
|
||||||
|
)
|
|
@ -9,6 +9,7 @@ import net.codinux.banking.client.model.*
|
||||||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||||
import net.codinux.banking.client.model.options.RetrieveTransactions
|
import net.codinux.banking.client.model.options.RetrieveTransactions
|
||||||
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
||||||
|
import net.codinux.banking.client.model.request.TransferMoneyRequestForUser
|
||||||
import net.codinux.banking.client.model.response.*
|
import net.codinux.banking.client.model.response.*
|
||||||
import net.codinux.banking.client.service.BankingModelService
|
import net.codinux.banking.client.service.BankingModelService
|
||||||
import net.codinux.banking.dataaccess.BankingRepository
|
import net.codinux.banking.dataaccess.BankingRepository
|
||||||
|
@ -22,7 +23,8 @@ import net.codinux.banking.ui.model.AccountTransactionViewModel
|
||||||
import net.codinux.banking.ui.model.BankInfo
|
import net.codinux.banking.ui.model.BankInfo
|
||||||
import net.codinux.banking.ui.model.TanChallengeReceived
|
import net.codinux.banking.ui.model.TanChallengeReceived
|
||||||
import net.codinux.banking.ui.model.error.*
|
import net.codinux.banking.ui.model.error.*
|
||||||
import net.codinux.banking.ui.model.events.AccountTransactionsRetrieved
|
import net.codinux.banking.ui.model.events.AccountTransactionsRetrievedEvent
|
||||||
|
import net.codinux.banking.ui.model.events.TransferredMoneyEvent
|
||||||
import net.codinux.banking.ui.state.UiState
|
import net.codinux.banking.ui.state.UiState
|
||||||
import net.codinux.csv.reader.CsvReader
|
import net.codinux.csv.reader.CsvReader
|
||||||
import net.codinux.log.logger
|
import net.codinux.log.logger
|
||||||
|
@ -81,7 +83,7 @@ class BankingService(
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
log.error(e) { "Could not add account for ${bank.name} $loginName" }
|
log.error(e) { "Could not add account for ${bank.name} $loginName" }
|
||||||
|
|
||||||
uiState.applicationErrorOccurred(ErroneousAction.AddAccount, e)
|
uiState.applicationErrorOccurred(ErroneousAction.AddAccount, null, e)
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -157,7 +159,7 @@ class BankingService(
|
||||||
}
|
}
|
||||||
|
|
||||||
val transactionsViewModel = updateTransactionsInUi(newTransactionsEntities)
|
val transactionsViewModel = updateTransactionsInUi(newTransactionsEntities)
|
||||||
uiState.fireNewTransactionsRetrieved(AccountTransactionsRetrieved(user, account, transactionsViewModel))
|
uiState.dispatchNewTransactionsRetrieved(AccountTransactionsRetrievedEvent(user, account, transactionsViewModel))
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
log.error(e) { "Could not save updated account transactions for user $user" }
|
log.error(e) { "Could not save updated account transactions for user $user" }
|
||||||
|
@ -175,6 +177,29 @@ class BankingService(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun transferMoney(user: UserAccountEntity, account: BankAccountEntity,
|
||||||
|
recipientName: String, recipientAccountIdentifier: String, amount: Amount, currency: String,
|
||||||
|
paymentReference: String? = null, instantTransfer: Boolean = false, recipientBankIdentifier: String? = null): Boolean {
|
||||||
|
val response = client.transferMoneyAsync(TransferMoneyRequestForUser(
|
||||||
|
user.bankCode, user.loginName, user.password!!,
|
||||||
|
BankAccountIdentifier(account.identifier, account.subAccountNumber, account.iban), // TODO: use BankingClient's one
|
||||||
|
recipientName, recipientAccountIdentifier, recipientBankIdentifier,
|
||||||
|
amount, "EUR",
|
||||||
|
paymentReference, instantTransfer
|
||||||
|
))
|
||||||
|
|
||||||
|
if (response.error != null) {
|
||||||
|
handleUnsuccessfulBankingClientResponse(BankingClientAction.TransferMoney, response)
|
||||||
|
} else if (response.type == ResponseType.Success) {
|
||||||
|
uiState.dispatchTransferredMoney(TransferredMoneyEvent(recipientName, amount, currency))
|
||||||
|
|
||||||
|
updateAccountTransactions(user, account)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.type == ResponseType.Success
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun handleUnsuccessfulBankingClientResponse(action: BankingClientAction, response: Response<*>) {
|
private fun handleUnsuccessfulBankingClientResponse(action: BankingClientAction, response: Response<*>) {
|
||||||
log.error { "$action was not successful: $response" }
|
log.error { "$action was not successful: $response" }
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,8 @@ import net.codinux.banking.ui.model.TanChallengeReceived
|
||||||
import net.codinux.banking.ui.model.error.ApplicationError
|
import net.codinux.banking.ui.model.error.ApplicationError
|
||||||
import net.codinux.banking.ui.model.error.BankingClientError
|
import net.codinux.banking.ui.model.error.BankingClientError
|
||||||
import net.codinux.banking.ui.model.error.ErroneousAction
|
import net.codinux.banking.ui.model.error.ErroneousAction
|
||||||
import net.codinux.banking.ui.model.events.AccountTransactionsRetrieved
|
import net.codinux.banking.ui.model.events.AccountTransactionsRetrievedEvent
|
||||||
|
import net.codinux.banking.ui.model.events.TransferredMoneyEvent
|
||||||
|
|
||||||
class UiState : ViewModel() {
|
class UiState : ViewModel() {
|
||||||
|
|
||||||
|
@ -22,14 +23,17 @@ class UiState : ViewModel() {
|
||||||
|
|
||||||
val transactions = MutableStateFlow<List<AccountTransactionViewModel>>(emptyList())
|
val transactions = MutableStateFlow<List<AccountTransactionViewModel>>(emptyList())
|
||||||
|
|
||||||
val transactionsRetrievedEvents = MutableSharedFlow<AccountTransactionsRetrieved>()
|
val transactionsRetrievedEvents = MutableSharedFlow<AccountTransactionsRetrievedEvent>()
|
||||||
|
|
||||||
suspend fun fireNewTransactionsRetrieved(event: AccountTransactionsRetrieved) {
|
suspend fun dispatchNewTransactionsRetrieved(event: AccountTransactionsRetrievedEvent) {
|
||||||
coroutineScope {
|
transactionsRetrievedEvents.emit(event)
|
||||||
launch {
|
}
|
||||||
transactionsRetrievedEvents.emit(event)
|
|
||||||
}
|
|
||||||
}
|
val transferredMoneyEvents = MutableSharedFlow<TransferredMoneyEvent>()
|
||||||
|
|
||||||
|
suspend fun dispatchTransferredMoney(event: TransferredMoneyEvent) {
|
||||||
|
transferredMoneyEvents.emit(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,8 +45,11 @@ class UiState : ViewModel() {
|
||||||
|
|
||||||
val showAddAccountDialog = MutableStateFlow(false)
|
val showAddAccountDialog = MutableStateFlow(false)
|
||||||
|
|
||||||
|
val showTransferMoneyDialog = MutableStateFlow(false)
|
||||||
|
|
||||||
val showExportScreen = 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)
|
||||||
|
@ -50,9 +57,10 @@ class UiState : ViewModel() {
|
||||||
val applicationErrorOccurred = MutableStateFlow<ApplicationError?>(null)
|
val applicationErrorOccurred = MutableStateFlow<ApplicationError?>(null)
|
||||||
|
|
||||||
|
|
||||||
fun applicationErrorOccurred(erroneousAction: ErroneousAction, exception: Throwable, errorMessage: String? = null) {
|
fun applicationErrorOccurred(erroneousAction: ErroneousAction, errorMessage: String? = null, exception: Throwable? = null) {
|
||||||
val message = errorMessage
|
val message = errorMessage
|
||||||
?: exception.message ?: exception::class.simpleName // TODO: find a better way to get error message from exception
|
?: exception?.message // TODO: find a better way to get error message from exception
|
||||||
|
?: exception?.let { it::class.simpleName }
|
||||||
|
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
applicationErrorOccurred.value = ApplicationError(erroneousAction, message, exception)
|
applicationErrorOccurred.value = ApplicationError(erroneousAction, message, exception)
|
||||||
|
|
Loading…
Reference in New Issue