Implemented TransferMoneyDialog

This commit is contained in:
dankl 2020-01-12 00:28:16 +01:00 committed by dankito
parent c894d8d44a
commit 7fb9ff1496
11 changed files with 368 additions and 12 deletions

View File

@ -1,5 +1,6 @@
package net.dankito.banking.ui.javafx.dialogs.mainwindow.controls package net.dankito.banking.ui.javafx.dialogs.mainwindow.controls
import javafx.beans.property.SimpleBooleanProperty
import javafx.scene.input.KeyCode import javafx.scene.input.KeyCode
import javafx.scene.input.KeyCodeCombination import javafx.scene.input.KeyCodeCombination
import javafx.scene.input.KeyCombination import javafx.scene.input.KeyCombination
@ -9,6 +10,18 @@ import tornadofx.*
open class MainMenuBar(protected val presenter: MainWindowPresenter) : View() { open class MainMenuBar(protected val presenter: MainWindowPresenter) : View() {
protected val areAccountsThatCanTransferMoneyAdded = SimpleBooleanProperty()
init {
presenter.addAccountAddedListener {
checkIfAreAccountsThatCanTransferMoneyAdded()
}
checkIfAreAccountsThatCanTransferMoneyAdded()
}
override val root = override val root =
menubar { menubar {
minHeight = 30.0 minHeight = 30.0
@ -16,9 +29,15 @@ open class MainMenuBar(protected val presenter: MainWindowPresenter) : View() {
menu(messages["main.window.menu.file"]) { menu(messages["main.window.menu.file"]) {
menu(messages["main.window.menu.file.new"]) { menu(messages["main.window.menu.file.new"]) {
item(messages["main.window.menu.file.new.account"], KeyCodeCombination(KeyCode.A, KeyCombination.SHORTCUT_DOWN)) { item(messages["main.window.menu.file.new.account"], KeyCodeCombination(KeyCode.E, KeyCombination.SHORTCUT_DOWN)) {
action { presenter.showAddAccountDialog() } action { presenter.showAddAccountDialog() }
} }
item(messages["main.window.menu.file.new.cash.transfer"], KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN)) {
enableWhen(areAccountsThatCanTransferMoneyAdded)
action { presenter.showTransferMoneyDialog() }
}
} }
separator() separator()
@ -29,4 +48,9 @@ open class MainMenuBar(protected val presenter: MainWindowPresenter) : View() {
} }
} }
protected open fun checkIfAreAccountsThatCanTransferMoneyAdded() {
areAccountsThatCanTransferMoneyAdded.value = presenter.accounts.isNotEmpty() // TODO: add check if they support transferring money
}
} }

View File

@ -12,6 +12,7 @@ check=Check
main.window.menu.file=File main.window.menu.file=File
main.window.menu.file.new=New... main.window.menu.file.new=New...
main.window.menu.file.new.account=Account main.window.menu.file.new.account=Account
main.window.menu.file.new.cash.transfer=Cash transfer
main.window.menu.file.quit=Quit main.window.menu.file.quit=Quit
@ -50,3 +51,17 @@ enter.tan.dialog.enter.tan.label=TAN:
enter.tan.dialog.error.could.not.decode.tan.image=Could not decode flicker code or QR code / PhotoTan. Most likely an internal error:\n%s. enter.tan.dialog.error.could.not.decode.tan.image=Could not decode flicker code or QR code / PhotoTan. Most likely an internal error:\n%s.
enter.tan.dialog.tan.medium.successfully.changed=TAN medium successfully changed to \'%s\'. enter.tan.dialog.tan.medium.successfully.changed=TAN medium successfully changed to \'%s\'.
enter.tan.dialog.tan.error.changing.tan.medium=Could not change TAN medium to \'%s\':\n%s. enter.tan.dialog.tan.error.changing.tan.medium=Could not change TAN medium to \'%s\':\n%s.
transfer.money.dialog.title=Transfer money
transfer.money.dialog.remittee.name.label=Remittee:
transfer.money.dialog.remittee.iban.label=IBAN:
transfer.money.dialog.remittee.bank.label=Bank:
transfer.money.dialog.remittee.bic.label=BIC:
transfer.money.dialog.amount.label=Amount:
transfer.money.dialog.usage.label=Usage:
transfer.money.dialog.transfer.money.label=Transfer
transfer.money.dialog.bank.name.will.be.entered.automatically=Will be entered automatically
transfer.money.dialog.bank.not.found.for.iban=No bank found for this IBAN
transfer.money.dialog.message.transfer.cash.success=Successfully transferred %.02f %s to %s
transfer.money.dialog.message.transfer.cash.error=Could not transfer %.02f %s to %s: %s

View File

@ -12,6 +12,7 @@ check=Überprüfen
main.window.menu.file=Datei main.window.menu.file=Datei
main.window.menu.file.new=Neu... main.window.menu.file.new=Neu...
main.window.menu.file.new.account=Konto main.window.menu.file.new.account=Konto
main.window.menu.file.new.cash.transfer=Überweisung
main.window.menu.file.quit=Beenden main.window.menu.file.quit=Beenden
@ -50,3 +51,17 @@ enter.tan.dialog.enter.tan.label=TAN:
enter.tan.dialog.error.could.not.decode.tan.image=Flickercode bzw. QR-Code / PhotoTan konnte nicht dekodiert werden. Höchst wahrscheinlich ein interner Fehler:\n%s. enter.tan.dialog.error.could.not.decode.tan.image=Flickercode bzw. QR-Code / PhotoTan konnte nicht dekodiert werden. Höchst wahrscheinlich ein interner Fehler:\n%s.
enter.tan.dialog.tan.medium.successfully.changed=TAN Medium erfolgreich geändert zu \'%s\'. enter.tan.dialog.tan.medium.successfully.changed=TAN Medium erfolgreich geändert zu \'%s\'.
enter.tan.dialog.tan.error.changing.tan.medium=TAN Medium konnte nicht geändert werden zu \'%s\':\n%s. enter.tan.dialog.tan.error.changing.tan.medium=TAN Medium konnte nicht geändert werden zu \'%s\':\n%s.
transfer.money.dialog.title=Neue Überweisung
transfer.money.dialog.remittee.name.label=Begünstigter (Name oder Firma):
transfer.money.dialog.remittee.iban.label=IBAN:
transfer.money.dialog.remittee.bank.label=Bank:
transfer.money.dialog.remittee.bic.label=BIC:
transfer.money.dialog.amount.label=Betrag:
transfer.money.dialog.usage.label=Verwendungszweck:
transfer.money.dialog.transfer.money.label=Überweisen
transfer.money.dialog.bank.name.will.be.entered.automatically=Wird automatisch eingetragen
transfer.money.dialog.bank.not.found.for.iban=Für diese IBAN wurde keine Bank gefunden
transfer.money.dialog.message.transfer.cash.success=%.02f %s erfolgreich an %s überwiesen.
transfer.money.dialog.message.transfer.cash.error=%.02f %s konnten nicht an %s überwiesen werden: %s

View File

@ -2,8 +2,11 @@ package net.dankito.banking.ui.javafx
import net.dankito.banking.ui.IRouter import net.dankito.banking.ui.IRouter
import net.dankito.banking.ui.javafx.dialogs.AddAccountDialog import net.dankito.banking.ui.javafx.dialogs.AddAccountDialog
import net.dankito.banking.ui.javafx.dialogs.cashtransfer.TransferMoneyDialog
import net.dankito.banking.ui.javafx.dialogs.tan.EnterTanDialog import net.dankito.banking.ui.javafx.dialogs.tan.EnterTanDialog
import net.dankito.banking.ui.model.Account import net.dankito.banking.ui.model.Account
import net.dankito.banking.ui.model.BankAccount
import net.dankito.banking.ui.model.parameters.TransferMoneyData
import net.dankito.banking.ui.model.tan.EnterTanGeneratorAtcResult import net.dankito.banking.ui.model.tan.EnterTanGeneratorAtcResult
import net.dankito.banking.ui.model.tan.EnterTanResult import net.dankito.banking.ui.model.tan.EnterTanResult
import net.dankito.banking.ui.model.tan.TanChallenge import net.dankito.banking.ui.model.tan.TanChallenge
@ -42,4 +45,8 @@ open class RouterJavaFx : IRouter {
return EnterTanGeneratorAtcResult.userDidNotEnterTan() return EnterTanGeneratorAtcResult.userDidNotEnterTan()
} }
override fun showTransferMoneyDialog(presenter: MainWindowPresenter, preselectedBankAccount: BankAccount?, preselectedValues: TransferMoneyData?) {
TransferMoneyDialog(presenter, preselectedBankAccount, preselectedValues).show(messages["transfer.money.dialog.title"])
}
} }

View File

@ -62,8 +62,7 @@ open class AccountTransactionsControlView(
addButton { addButton {
useMaxHeight = true useMaxHeight = true
// TODO action { presenter.showTransferMoneyDialog() }
// action { presenter.showTransferMoneyDialog() }
hboxConstraints { hboxConstraints {
marginLeft = 12.0 marginLeft = 12.0

View File

@ -8,6 +8,7 @@ import javafx.scene.input.ContextMenuEvent
import javafx.scene.input.MouseButton import javafx.scene.input.MouseButton
import javafx.scene.input.MouseEvent import javafx.scene.input.MouseEvent
import net.dankito.banking.ui.model.AccountTransaction import net.dankito.banking.ui.model.AccountTransaction
import net.dankito.banking.ui.model.parameters.TransferMoneyData
import net.dankito.banking.ui.model.responses.GetTransactionsResponse import net.dankito.banking.ui.model.responses.GetTransactionsResponse
import net.dankito.banking.ui.presenter.MainWindowPresenter import net.dankito.banking.ui.presenter.MainWindowPresenter
import tornadofx.* import tornadofx.*
@ -104,8 +105,7 @@ open class AccountTransactionsView(private val presenter: MainWindowPresenter) :
} }
protected open fun showTransferMoneyDialog(transaction: AccountTransaction) { protected open fun showTransferMoneyDialog(transaction: AccountTransaction) {
// TODO: presenter.showTransferMoneyDialog(transaction.bankAccount, TransferMoneyData.fromAccountTransaction(transaction))
// presenter.showTransferMoneyDialog(transaction.bankAccount, TransferMoneyData.fromAccountTransaction(transaction))
} }

View File

@ -0,0 +1,285 @@
package net.dankito.banking.ui.javafx.dialogs.cashtransfer
import javafx.beans.property.SimpleBooleanProperty
import javafx.beans.property.SimpleDoubleProperty
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty
import javafx.geometry.Insets
import javafx.geometry.Pos
import javafx.scene.layout.Priority
import net.dankito.banking.ui.javafx.dialogs.JavaFxDialogService
import net.dankito.banking.ui.model.BankAccount
import net.dankito.banking.ui.model.parameters.TransferMoneyData
import net.dankito.banking.ui.model.responses.BankingClientResponse
import net.dankito.banking.ui.presenter.MainWindowPresenter
import net.dankito.fints.messages.segmente.implementierte.sepa.ISepaMessageCreator
import net.dankito.fints.messages.segmente.implementierte.sepa.SepaMessageCreator
import net.dankito.fints.model.BankInfo
import net.dankito.utils.javafx.ui.controls.doubleTextfield
import net.dankito.utils.javafx.ui.dialogs.Window
import net.dankito.utils.javafx.ui.extensions.fixedHeight
import net.dankito.utils.javafx.ui.extensions.fixedWidth
import tornadofx.*
open class TransferMoneyDialog @JvmOverloads constructor(
protected val presenter: MainWindowPresenter,
preselectedBankAccount: BankAccount? = null,
preselectedValues: TransferMoneyData? = null
) : Window() {
companion object {
private val TextFieldHeight = 32.0
private val ButtonHeight = 40.0
private val ButtonWidth = 150.0
}
protected val selectedBankAccount = SimpleObjectProperty<BankAccount>(preselectedBankAccount ?: presenter.bankAccounts.firstOrNull())
protected val remitteeName = SimpleStringProperty(preselectedValues?.creditorName ?: "")
protected val remitteeIban = SimpleStringProperty(preselectedValues?.creditorIban ?: "")
protected val remitteeBank = SimpleObjectProperty<BankInfo>()
protected val remitteeBankName = SimpleStringProperty()
protected val remitteeBic = SimpleStringProperty(preselectedValues?.creditorBic ?: "")
protected val amount = SimpleDoubleProperty(preselectedValues?.amount?.toDouble() ?: 0.0)
protected val usage = SimpleStringProperty(preselectedValues?.usage ?: "")
protected val requiredDataEntered = SimpleBooleanProperty(false)
protected val sepaMessageCreator: ISepaMessageCreator = SepaMessageCreator()
protected val dialogService = JavaFxDialogService()
init {
remitteeName.addListener { _, _, _ -> checkIfRequiredDataEnteredOnUiThread() }
remitteeIban.addListener { _, _, newValue -> tryToGetBicFromIban(newValue) }
remitteeBic.addListener { _, _, _ -> checkIfRequiredDataEnteredOnUiThread() }
amount.addListener { _, _, _ -> checkIfRequiredDataEnteredOnUiThread() }
usage.addListener { _, _, _ -> checkIfRequiredDataEnteredOnUiThread() }
}
override val root = vbox {
prefWidth = 650.0
form {
vboxConstraints {
vGrow = Priority.ALWAYS
}
fieldset {
field(messages["transfer.money.dialog.remittee.name.label"]) {
textfield(this@TransferMoneyDialog.remitteeName) {
fixedHeight = TextFieldHeight
}
}
field(messages["transfer.money.dialog.remittee.iban.label"]) {
textfield(remitteeIban) {
fixedHeight = TextFieldHeight
paddingLeft = 8.0
if (this@TransferMoneyDialog.remitteeName.value.isNotBlank()) {
runLater {
requestFocus()
}
}
}
}
field(messages["transfer.money.dialog.remittee.bank.label"]) {
fixedHeight = TextFieldHeight + 18.0
hboxConstraints {
marginTopBottom(8.0)
}
textfield(remitteeBankName) {
fixedHeight = TextFieldHeight
isDisable = true
paddingLeft = 8.0
}
}
field(messages["transfer.money.dialog.remittee.bic.label"]) {
fixedHeight = TextFieldHeight + 18.0
textfield(remitteeBic) {
fixedHeight = TextFieldHeight
isDisable = true
paddingLeft = 8.0
}
}
field(messages["transfer.money.dialog.amount.label"]) {
anchorpane {
doubleTextfield(amount, false) {
fixedHeight = TextFieldHeight
fixedWidth = 100.0
alignment = Pos.CENTER_RIGHT
if (this@TransferMoneyDialog.remitteeName.value.isNotBlank() && remitteeIban.value.isNotBlank()) {
runLater {
requestFocus()
}
}
anchorpaneConstraints {
topAnchor = 0.0
rightAnchor = 20.0
bottomAnchor = 0.0
}
}
label(selectedBankAccount.value?.currency ?: "") {
anchorpaneConstraints {
topAnchor = 0.0
rightAnchor = 0.0
bottomAnchor = 0.0
}
}
}
}
field(messages["transfer.money.dialog.usage.label"]) {
textfield(usage) {
fixedHeight = TextFieldHeight
}
}
}
}
hbox {
alignment = Pos.CENTER_RIGHT
button(messages["cancel"]) {
prefHeight = ButtonHeight
prefWidth = ButtonWidth
isCancelButton = true
action { cancelCashTransfer() }
hboxConstraints {
margin = Insets(6.0, 0.0, 4.0, 0.0)
}
}
button(messages["transfer.money.dialog.transfer.money.label"]) {
prefHeight = ButtonHeight
prefWidth = ButtonWidth
isDefaultButton = true
enableWhen(requiredDataEntered)
action { transferMoney() }
hboxConstraints {
margin = Insets(6.0, 4.0, 4.0, 12.0)
}
}
}
tryToGetBicFromIban(remitteeIban.value)
}
protected open fun tryToGetBicFromIban(enteredIban: String) {
presenter.findUniqueBankForIbanAsync(enteredIban) { foundBank ->
runLater {
showValuesForFoundBankOnUiThread(foundBank, enteredIban)
}
}
}
protected open fun showValuesForFoundBankOnUiThread(firstFoundBank: BankInfo?, enteredIban: String) {
remitteeBank.value = firstFoundBank
remitteeBankName.value = determineFoundBankLabel(enteredIban, firstFoundBank)
remitteeBic.value = firstFoundBank?.bic ?: messages["transfer.money.dialog.bank.name.will.be.entered.automatically"]
checkIfRequiredDataEnteredOnUiThread()
}
protected open fun determineFoundBankLabel(enteredIban: String?, bankInfo: BankInfo?): String? {
return if (bankInfo != null) {
return bankInfo.name + " " + bankInfo.city
}
else if (enteredIban.isNullOrBlank()) {
messages["transfer.money.dialog.bank.name.will.be.entered.automatically"]
}
else {
messages["transfer.money.dialog.bank.not.found.for.iban"]
}
}
protected open fun checkIfRequiredDataEnteredOnUiThread() {
requiredDataEntered.value =
remitteeName.value.isNotBlank()
&& sepaMessageCreator.containsOnlyAllowedCharacters(remitteeName.value) // TODO: show error message for illegal characters
&& remitteeIban.value.isNotEmpty() // TODO: check if it is of length > 12, in Germany > 22?
&& remitteeBic.value.isNotEmpty() // TODO: check if it is of length is 8 or 11?
&& amount.value > 0
&& sepaMessageCreator.containsOnlyAllowedCharacters(usage.value) // TODO: show error message for illegal characters
}
protected open fun cancelCashTransfer() {
close()
}
protected open fun transferMoney() {
remitteeBank.value?.let { remitteeBank ->
val bankAccount = selectedBankAccount.value
val data = TransferMoneyData(
remitteeName.value,
remitteeIban.value,
remitteeBic.value,
amount.value.toBigDecimal(),
usage.value
)
presenter.transferMoneyAsync(bankAccount, data) {
runLater {
handleTransferMoneyResultOnUiThread(bankAccount, data, it)
}
}
}
}
protected open fun handleTransferMoneyResultOnUiThread(bankAccount: BankAccount, transferData: TransferMoneyData, response: BankingClientResponse) {
val currency = bankAccount.currency
if (response.isSuccessful) {
dialogService.showInfoMessage(String.format(messages["transfer.money.dialog.message.transfer.cash.success"],
transferData.amount, currency, transferData.creditorName), null, currentStage)
}
else {
dialogService.showErrorMessage(String.format(messages["transfer.money.dialog.message.transfer.cash.error"],
transferData.amount, currency, transferData.creditorName, response.errorToShowToUser), null, response.error, currentStage)
}
close()
}
}

View File

@ -1,6 +1,8 @@
package net.dankito.banking.ui package net.dankito.banking.ui
import net.dankito.banking.ui.model.Account import net.dankito.banking.ui.model.Account
import net.dankito.banking.ui.model.BankAccount
import net.dankito.banking.ui.model.parameters.TransferMoneyData
import net.dankito.banking.ui.model.tan.EnterTanGeneratorAtcResult import net.dankito.banking.ui.model.tan.EnterTanGeneratorAtcResult
import net.dankito.banking.ui.model.tan.EnterTanResult import net.dankito.banking.ui.model.tan.EnterTanResult
import net.dankito.banking.ui.model.tan.TanChallenge import net.dankito.banking.ui.model.tan.TanChallenge
@ -16,4 +18,6 @@ interface IRouter {
fun getAtcFromUserFromNonUiThread(tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult fun getAtcFromUserFromNonUiThread(tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult
fun showTransferMoneyDialog(presenter: MainWindowPresenter, preselectedBankAccount: BankAccount?, preselectedValues: TransferMoneyData?)
} }

View File

@ -222,6 +222,10 @@ open class MainWindowPresenter(
router.showAddAccountDialog(this) router.showAddAccountDialog(this)
} }
open fun showTransferMoneyDialog(preselectedBankAccount: BankAccount? = null, preselectedValues: TransferMoneyData? = null) {
router.showTransferMoneyDialog(this, preselectedBankAccount, preselectedValues)
}
protected open fun getClientForAccount(account: Account): IBankingClient? { protected open fun getClientForAccount(account: Account): IBankingClient? {
clientsForAccounts.get(account)?.let { client -> clientsForAccounts.get(account)?.let { client ->

View File

@ -4,8 +4,11 @@ import android.support.v7.app.AppCompatActivity
import net.dankito.banking.fints4java.android.ui.dialogs.AddAccountDialog import net.dankito.banking.fints4java.android.ui.dialogs.AddAccountDialog
import net.dankito.banking.fints4java.android.ui.dialogs.EnterAtcDialog import net.dankito.banking.fints4java.android.ui.dialogs.EnterAtcDialog
import net.dankito.banking.fints4java.android.ui.dialogs.EnterTanDialog import net.dankito.banking.fints4java.android.ui.dialogs.EnterTanDialog
import net.dankito.banking.fints4java.android.ui.dialogs.TransferMoneyDialog
import net.dankito.banking.ui.IRouter import net.dankito.banking.ui.IRouter
import net.dankito.banking.ui.model.Account import net.dankito.banking.ui.model.Account
import net.dankito.banking.ui.model.BankAccount
import net.dankito.banking.ui.model.parameters.TransferMoneyData
import net.dankito.banking.ui.model.tan.EnterTanGeneratorAtcResult import net.dankito.banking.ui.model.tan.EnterTanGeneratorAtcResult
import net.dankito.banking.ui.model.tan.EnterTanResult import net.dankito.banking.ui.model.tan.EnterTanResult
import net.dankito.banking.ui.model.tan.TanChallenge import net.dankito.banking.ui.model.tan.TanChallenge
@ -53,4 +56,8 @@ open class RouterAndroid(protected val activity: AppCompatActivity) : IRouter {
return result.get() return result.get()
} }
override fun showTransferMoneyDialog(presenter: MainWindowPresenter, preselectedBankAccount: BankAccount?, preselectedValues: TransferMoneyData?) {
TransferMoneyDialog().show(activity, presenter, preselectedBankAccount, preselectedValues)
}
} }

View File

@ -6,7 +6,6 @@ import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
import android.support.v7.app.AlertDialog import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.support.v7.widget.SearchView import android.support.v7.widget.SearchView
@ -16,7 +15,6 @@ import android.widget.EditText
import net.dankito.banking.fints4java.android.MainActivity import net.dankito.banking.fints4java.android.MainActivity
import net.dankito.banking.fints4java.android.R import net.dankito.banking.fints4java.android.R
import net.dankito.banking.fints4java.android.ui.adapter.AccountTransactionAdapter import net.dankito.banking.fints4java.android.ui.adapter.AccountTransactionAdapter
import net.dankito.banking.fints4java.android.ui.dialogs.TransferMoneyDialog
import net.dankito.banking.ui.model.AccountTransaction import net.dankito.banking.ui.model.AccountTransaction
import net.dankito.banking.ui.model.parameters.TransferMoneyData import net.dankito.banking.ui.model.parameters.TransferMoneyData
import net.dankito.banking.ui.model.responses.GetTransactionsResponse import net.dankito.banking.ui.model.responses.GetTransactionsResponse
@ -165,9 +163,7 @@ class HomeFragment : Fragment() {
private fun showTransferMoneyDialog() { private fun showTransferMoneyDialog() {
transactionAdapter.selectedTransaction?.let { selectedTransaction -> transactionAdapter.selectedTransaction?.let { selectedTransaction ->
(context as? AppCompatActivity)?.let { activity -> presenter.showTransferMoneyDialog(selectedTransaction.bankAccount, mapPreselectedValues(selectedTransaction))
TransferMoneyDialog().show(activity, presenter, selectedTransaction.bankAccount, mapPreselectedValues(selectedTransaction))
}
} }
} }