Added EnterTanDialog
This commit is contained in:
parent
fb6fc8688e
commit
9496836b76
|
@ -17,4 +17,6 @@ dependencies {
|
|||
api project(':BankingJavaFxControls')
|
||||
|
||||
api project(':fints4javaBankingClient')
|
||||
|
||||
implementation "ch.qos.logback:logback-classic:$logbackVersion"
|
||||
}
|
|
@ -22,5 +22,12 @@ add.account.dialog.customer.id=Customer Id (Account number)
|
|||
add.account.dialog.customer.id.hint=The user name you use for logging in into online banking
|
||||
add.account.dialog.password=Online banking password
|
||||
add.account.dialog.password.hint=The password you use for logging in into online banking
|
||||
add.account.dialog.could.not.add.account=Could not add account for bank code '%s' and customer id '%s': %s
|
||||
add.account.dialog.add.account.success=Successfully added accounterror.message.could.not.retrieve.accounting.entries=Could not get entries for account '%s': %s
|
||||
add.account.dialog.error.could.not.add.account=Could not add account for bank code '%s' and customer id '%s': %s
|
||||
add.account.dialog.successfully.added.account=Successfully added account.\r\n\r\nWould you like to fetch all account transactions now? If so entering a TAN is required.
|
||||
add.account.dialog.successfully.added.account.bank.supports.retrieving.transactions.of.last.90.days.without.tan=Successfully added account.\r\n\r\nYour bank supports retrieving account transactions of last 90 days without TAN. These are already displayed in background.\r\n\r\nWould you like to fetch all account transactions now? If so entering a TAN is required.
|
||||
|
||||
|
||||
enter.tan.dialog.title=TAN required
|
||||
enter.tan.dialog.size.label=Size:
|
||||
enter.tan.dialog.frequency.label=Speed:
|
||||
enter.tan.dialog.enter.tan.label=TAN:
|
|
@ -22,5 +22,12 @@ add.account.dialog.customer.id=Kundennummer (Kontonummer)
|
|||
add.account.dialog.customer.id.hint=Der Nutzernamen den Sie fürs Online Banking verwenden
|
||||
add.account.dialog.password=Online Banking Passwort
|
||||
add.account.dialog.password.hint=Das Passwort das Sie fürs Online Banking verwenden
|
||||
add.account.dialog.could.not.add.account=Konnte Kontoinformation zu Bankleitzahl '%s' und Nutzername '%s' nicht finden: %s
|
||||
add.account.dialog.add.account.success=Hinzufügen des Kontos war erfolgreich.
|
||||
add.account.dialog.error.could.not.add.account=Konnte Kontoinformation zu Bankleitzahl '%s' und Nutzername '%s' nicht finden: %s
|
||||
add.account.dialog.successfully.added.account=Hinzufügen des Kontos war erfolgreich.\r\n\r\nMöchten Sie nun die Kontoumsätze abholfen? Falls ja muss eine TAN eingegeben werden.
|
||||
add.account.dialog.successfully.added.account.bank.supports.retrieving.transactions.of.last.90.days.without.tan=Hinzufügen des Kontos war erfolgreich.\r\n\r\nIhre Bank unterstützt das Abholen der Kontoumätze der letzten 90 Tage ohne TAN. Diese werden im Hintergrund bereits angezeigt.\r\n\r\nMöchten Sie nun alle Kontoumsätze abholfen? Falls ja muss eine TAN eingegeben werden.
|
||||
|
||||
|
||||
enter.tan.dialog.title=TAN wird benötigt
|
||||
enter.tan.dialog.size.label=Größe:
|
||||
enter.tan.dialog.frequency.label=Geschwindigkeit:
|
||||
enter.tan.dialog.enter.tan.label=TAN:
|
|
@ -2,14 +2,18 @@ package net.dankito.banking.ui.javafx
|
|||
|
||||
import net.dankito.banking.ui.IRouter
|
||||
import net.dankito.banking.ui.javafx.dialogs.AddAccountDialog
|
||||
import net.dankito.banking.ui.javafx.dialogs.tan.EnterTanDialog
|
||||
import net.dankito.banking.ui.model.Account
|
||||
import net.dankito.banking.ui.model.tan.EnterTanGeneratorAtcResult
|
||||
import net.dankito.banking.ui.model.tan.EnterTanResult
|
||||
import net.dankito.banking.ui.model.tan.TanChallenge
|
||||
import net.dankito.banking.ui.model.tan.TanGeneratorTanMedium
|
||||
import net.dankito.banking.ui.presenter.MainWindowPresenter
|
||||
import tornadofx.FX
|
||||
import tornadofx.FX.Companion.messages
|
||||
import tornadofx.get
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
|
||||
open class RouterJavaFx : IRouter {
|
||||
|
@ -19,7 +23,19 @@ open class RouterJavaFx : IRouter {
|
|||
}
|
||||
|
||||
override fun getTanFromUserFromNonUiThread(account: Account, tanChallenge: TanChallenge, presenter: MainWindowPresenter): EnterTanResult {
|
||||
return EnterTanResult.userDidNotEnterTan()
|
||||
val enteredTan = AtomicReference<EnterTanResult>(null)
|
||||
val tanEnteredLatch = CountDownLatch(1)
|
||||
|
||||
FX.runAndWait {
|
||||
EnterTanDialog(account, tanChallenge, presenter) {
|
||||
enteredTan.set(it)
|
||||
tanEnteredLatch.countDown()
|
||||
}.show()
|
||||
}
|
||||
|
||||
try { tanEnteredLatch.await() } catch (ignored: Exception) { }
|
||||
|
||||
return enteredTan.get()
|
||||
}
|
||||
|
||||
override fun getAtcFromUserFromNonUiThread(tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult {
|
||||
|
|
|
@ -4,7 +4,10 @@ import javafx.beans.property.SimpleBooleanProperty
|
|||
import javafx.beans.property.SimpleStringProperty
|
||||
import javafx.geometry.Insets
|
||||
import javafx.geometry.Pos
|
||||
import javafx.scene.control.Alert
|
||||
import javafx.scene.control.ButtonType
|
||||
import javafx.scene.control.Tooltip
|
||||
import javafx.scene.layout.Priority
|
||||
import javafx.scene.paint.Color
|
||||
import javafx.scene.text.Font
|
||||
import javafx.scene.text.FontWeight
|
||||
|
@ -31,25 +34,26 @@ open class AddAccountDialog(protected val presenter: MainWindowPresenter) : Wind
|
|||
}
|
||||
|
||||
|
||||
private val bankCode = SimpleStringProperty("")
|
||||
protected val dialogService = JavaFxDialogService()
|
||||
|
||||
|
||||
protected val bankCode = SimpleStringProperty("")
|
||||
|
||||
protected var selectedBank: BankInfo? = null
|
||||
|
||||
private val customerId = SimpleStringProperty("")
|
||||
protected val customerId = SimpleStringProperty("")
|
||||
|
||||
private val password = SimpleStringProperty("")
|
||||
protected val password = SimpleStringProperty("")
|
||||
|
||||
private val requiredDataHasBeenEntered = SimpleBooleanProperty(false)
|
||||
protected val requiredDataHasBeenEntered = SimpleBooleanProperty(false)
|
||||
|
||||
|
||||
private val checkEnteredCredentialsResult = SimpleStringProperty("")
|
||||
protected val checkEnteredCredentialsResult = SimpleStringProperty("")
|
||||
|
||||
private val isEnteredCredentialsResultVisible = SimpleBooleanProperty(false)
|
||||
|
||||
private val didEnteredCredentialsMatch = SimpleBooleanProperty(false)
|
||||
protected val isEnteredCredentialsResultVisible = SimpleBooleanProperty(false)
|
||||
|
||||
|
||||
private val checkCredentialsButton = UpdateButton(messages["check"])
|
||||
protected val checkCredentialsButton = UpdateButton(messages["check"])
|
||||
|
||||
|
||||
init {
|
||||
|
@ -120,30 +124,27 @@ open class AddAccountDialog(protected val presenter: MainWindowPresenter) : Wind
|
|||
}
|
||||
|
||||
label(checkEnteredCredentialsResult) {
|
||||
useMaxHeight = true
|
||||
|
||||
visibleWhen(isEnteredCredentialsResultVisible)
|
||||
ensureOnlyUsesSpaceIfVisible()
|
||||
|
||||
isWrapText = true
|
||||
font = Font(font.size + 1)
|
||||
|
||||
setBackgroundToColor(Color.RED)
|
||||
|
||||
paddingAll = 8.0
|
||||
|
||||
checkEnteredCredentialsResult.addListener { _, _, _ ->
|
||||
if (didEnteredCredentialsMatch.value) {
|
||||
setBackgroundToColor(Color.TRANSPARENT)
|
||||
}
|
||||
else {
|
||||
setBackgroundToColor(Color.RED)
|
||||
}
|
||||
|
||||
tooltip = Tooltip(checkEnteredCredentialsResult.value)
|
||||
|
||||
currentWindow?.sizeToScene()
|
||||
}
|
||||
|
||||
vboxConstraints {
|
||||
marginTop = 12.0
|
||||
marginBottom = 6.0
|
||||
|
||||
vGrow = Priority.ALWAYS
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,7 +152,8 @@ open class AddAccountDialog(protected val presenter: MainWindowPresenter) : Wind
|
|||
alignment = Pos.CENTER_RIGHT
|
||||
|
||||
button(messages["cancel"]) {
|
||||
prefHeight = ButtonHeight
|
||||
minHeight = ButtonHeight
|
||||
maxHeight = ButtonHeight
|
||||
prefWidth = ButtonWidth
|
||||
|
||||
isCancelButton = true
|
||||
|
@ -164,7 +166,8 @@ open class AddAccountDialog(protected val presenter: MainWindowPresenter) : Wind
|
|||
}
|
||||
|
||||
add(checkCredentialsButton.apply {
|
||||
prefHeight = ButtonHeight
|
||||
minHeight = ButtonHeight
|
||||
maxHeight = ButtonHeight
|
||||
prefWidth = ButtonWidth
|
||||
|
||||
isDefaultButton = true
|
||||
|
@ -203,6 +206,8 @@ open class AddAccountDialog(protected val presenter: MainWindowPresenter) : Wind
|
|||
|
||||
|
||||
protected open fun checkEnteredCredentials() {
|
||||
isEnteredCredentialsResultVisible.value = false
|
||||
|
||||
selectedBank?.let {
|
||||
presenter.addAccountAsync(it, customerId.value, password.value) { response ->
|
||||
runLater { handleAddAccountResultOnUiThread(response) }
|
||||
|
@ -211,21 +216,33 @@ open class AddAccountDialog(protected val presenter: MainWindowPresenter) : Wind
|
|||
}
|
||||
|
||||
protected open fun handleAddAccountResultOnUiThread(response: AddAccountResponse) {
|
||||
isEnteredCredentialsResultVisible.value = true
|
||||
didEnteredCredentialsMatch.value = response.isSuccessful
|
||||
val account = response.account
|
||||
|
||||
// TODO: in case of success show alert to ask if account transactions should get retrieved?
|
||||
|
||||
val message = if (response.isSuccessful) messages["add.account.dialog.add.account.success"]
|
||||
else String.format(messages["add.account.dialog.could.not.add.account"],
|
||||
account.bank.bankCode, account.customerId, response.errorToShowToUser)
|
||||
|
||||
checkEnteredCredentialsResult.value = message
|
||||
checkCredentialsButton.resetIsUpdating()
|
||||
|
||||
if (response.isSuccessful) {
|
||||
close()
|
||||
handleSuccessfullyAddedAccountResultOnUiThread(response)
|
||||
}
|
||||
else {
|
||||
val account = response.account
|
||||
|
||||
checkEnteredCredentialsResult.value = String.format(messages["add.account.dialog.error.could.not.add.account"],
|
||||
account.bank.bankCode, account.customerId, response.errorToShowToUser)
|
||||
|
||||
isEnteredCredentialsResultVisible.value = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSuccessfullyAddedAccountResultOnUiThread(response: AddAccountResponse) {
|
||||
val message = if (response.supportsRetrievingTransactionsOfLast90DaysWithoutTan) messages["add.account.dialog.successfully.added.account.bank.supports.retrieving.transactions.of.last.90.days.without.tan"]
|
||||
else messages["add.account.dialog.successfully.added.account"]
|
||||
|
||||
val userSelection = dialogService.showDialog(Alert.AlertType.CONFIRMATION, message, null, currentStage, ButtonType.YES, ButtonType.NO)
|
||||
|
||||
when (userSelection) {
|
||||
ButtonType.YES -> { presenter.getAccountTransactionsAsync(response.account) { } }
|
||||
else -> { } // nothing to do then, simply close dialog
|
||||
}
|
||||
|
||||
close()
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
package net.dankito.banking.ui.javafx.dialogs
|
||||
|
||||
import javafx.application.Platform
|
||||
import javafx.scene.control.Alert
|
||||
import javafx.scene.control.ButtonType
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.control.TextArea
|
||||
import javafx.scene.layout.GridPane
|
||||
import javafx.scene.layout.Priority
|
||||
import javafx.scene.layout.Region
|
||||
import javafx.scene.layout.VBox
|
||||
import javafx.stage.Screen
|
||||
import javafx.stage.Stage
|
||||
import javafx.stage.Window
|
||||
import tornadofx.FX
|
||||
import tornadofx.useMaxHeight
|
||||
import tornadofx.useMaxWidth
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
|
||||
|
||||
open class JavaFxDialogService {
|
||||
|
||||
open fun showInfoMessage(infoMessage: CharSequence, alertTitle: CharSequence?) {
|
||||
showInfoMessage(infoMessage, alertTitle, FX.primaryStage)
|
||||
}
|
||||
|
||||
open fun showInfoMessage(infoMessage: CharSequence, alertTitle: CharSequence?, owner: Stage?) {
|
||||
Platform.runLater { showInfoMessageOnUiThread(infoMessage, alertTitle, owner) }
|
||||
}
|
||||
|
||||
open fun showInfoMessageOnUiThread(infoMessage: CharSequence, alertTitle: CharSequence?, owner: Stage?) {
|
||||
val alert = createDialog(Alert.AlertType.INFORMATION, infoMessage, alertTitle, owner, ButtonType.OK)
|
||||
|
||||
alert.showAndWait()
|
||||
}
|
||||
|
||||
|
||||
open fun showErrorMessage(errorMessage: CharSequence, alertTitle: CharSequence?, exception: Exception?) {
|
||||
showErrorMessage(errorMessage, alertTitle, exception, null)
|
||||
}
|
||||
|
||||
open fun showErrorMessage(errorMessage: CharSequence, alertTitle: CharSequence?, exception: Exception?, owner: Stage?) {
|
||||
Platform.runLater { showErrorMessageOnUiThread(errorMessage, alertTitle, exception, owner) }
|
||||
}
|
||||
|
||||
open fun showErrorMessageOnUiThread(errorMessage: CharSequence, alertTitle: CharSequence?, exception: Exception?, owner: Stage?) {
|
||||
val alert = createDialog(Alert.AlertType.ERROR, errorMessage, alertTitle, owner, ButtonType.OK)
|
||||
|
||||
if (exception != null) {
|
||||
createExpandableException(alert, exception)
|
||||
}
|
||||
|
||||
alert.showAndWait()
|
||||
}
|
||||
|
||||
protected open fun createExpandableException(alert: Alert, exception: Exception) {
|
||||
val sw = StringWriter()
|
||||
val pw = PrintWriter(sw)
|
||||
exception.printStackTrace(pw)
|
||||
val exceptionText = sw.toString()
|
||||
|
||||
val label = Label("The exception stacktrace was:")
|
||||
|
||||
val textArea = TextArea(exceptionText)
|
||||
textArea.isEditable = false
|
||||
textArea.isWrapText = true
|
||||
|
||||
textArea.useMaxWidth = true
|
||||
textArea.useMaxHeight = true
|
||||
GridPane.setVgrow(textArea, Priority.ALWAYS)
|
||||
GridPane.setHgrow(textArea, Priority.ALWAYS)
|
||||
|
||||
val expContent = GridPane()
|
||||
expContent.useMaxWidth = true
|
||||
expContent.add(label, 0, 0)
|
||||
expContent.add(textArea, 0, 1)
|
||||
|
||||
// Set expandable Exception into the dialog pane.
|
||||
alert.dialogPane.expandableContent = expContent
|
||||
}
|
||||
|
||||
|
||||
open fun showDialog(alertType: Alert.AlertType, message: CharSequence, alertTitle: CharSequence? = null, owner: Stage? = FX.primaryStage, vararg buttons: ButtonType): ButtonType? {
|
||||
val dialog = createDialog(alertType, message, alertTitle, owner, *buttons)
|
||||
|
||||
val result = dialog.showAndWait()
|
||||
|
||||
return result.map { it }.orElse(null)
|
||||
}
|
||||
|
||||
open fun createDialog(alertType: Alert.AlertType, message: CharSequence, alertTitle: CharSequence?, owner: Stage?, vararg buttons: ButtonType): Alert {
|
||||
val alert = Alert(alertType)
|
||||
|
||||
(alertTitle as? String)?.let { alert.title = it }
|
||||
|
||||
owner?.let { alert.initOwner(it) }
|
||||
|
||||
(message as? String)?.let { setAlertContent(alert, it) }
|
||||
alert.headerText = null
|
||||
|
||||
alert.buttonTypes.setAll(*buttons)
|
||||
|
||||
return alert
|
||||
}
|
||||
|
||||
private fun setAlertContent(alert: Alert, content: String) {
|
||||
var maxWidth = Screen.getPrimary().visualBounds.width
|
||||
|
||||
if(alert.owner != null) {
|
||||
getScreenWindowLeftUpperCornerIsIn(alert.owner)?.let { ownersScreen ->
|
||||
maxWidth = ownersScreen.visualBounds.width
|
||||
}
|
||||
}
|
||||
|
||||
maxWidth *= 0.6 // set max width to 60 % of Screen width
|
||||
|
||||
val contentLabel = Label(content)
|
||||
contentLabel.isWrapText = true
|
||||
contentLabel.prefHeight = Region.USE_COMPUTED_SIZE
|
||||
contentLabel.useMaxHeight = true
|
||||
contentLabel.maxWidth = maxWidth
|
||||
|
||||
val contentPane = VBox(contentLabel)
|
||||
contentPane.prefHeight = Region.USE_COMPUTED_SIZE
|
||||
contentPane.useMaxHeight = true
|
||||
VBox.setVgrow(contentLabel, Priority.ALWAYS)
|
||||
|
||||
alert.dialogPane.prefHeight = Region.USE_COMPUTED_SIZE
|
||||
alert.dialogPane.useMaxHeight = true
|
||||
alert.dialogPane.maxWidth = maxWidth
|
||||
alert.dialogPane.content = contentPane
|
||||
}
|
||||
|
||||
protected open fun getScreenWindowLeftUpperCornerIsIn(window: Window): Screen? {
|
||||
val screens = Screen.getScreensForRectangle(window.x, window.y, 1.0, 1.0)
|
||||
if (screens.size > 0)
|
||||
return screens[0]
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
package net.dankito.banking.ui.javafx.dialogs.tan
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import javafx.geometry.Insets
|
||||
import javafx.geometry.Pos
|
||||
import net.dankito.banking.javafx.dialogs.tan.controls.ChipTanFlickerCodeView
|
||||
import net.dankito.banking.ui.model.Account
|
||||
import net.dankito.banking.ui.model.tan.EnterTanResult
|
||||
import net.dankito.banking.ui.model.tan.FlickerCodeTanChallenge
|
||||
import net.dankito.banking.ui.model.tan.TanChallenge
|
||||
import net.dankito.banking.ui.presenter.MainWindowPresenter
|
||||
import net.dankito.utils.javafx.ui.dialogs.Window
|
||||
import tornadofx.*
|
||||
|
||||
|
||||
open class EnterTanDialog(
|
||||
protected val account: Account,
|
||||
protected val challenge: TanChallenge,
|
||||
protected val presenter: MainWindowPresenter,
|
||||
protected val tanEnteredCallback: (EnterTanResult) -> Unit
|
||||
) : Window() {
|
||||
|
||||
companion object {
|
||||
private val ButtonHeight = 40.0
|
||||
private val ButtonWidth = 150.0
|
||||
}
|
||||
|
||||
|
||||
protected val enteredTan = SimpleStringProperty("")
|
||||
|
||||
|
||||
override val root = vbox {
|
||||
paddingAll = 4.0
|
||||
|
||||
(challenge as? FlickerCodeTanChallenge)?.let { flickerCodeTanChallenge ->
|
||||
hbox {
|
||||
alignment = Pos.CENTER
|
||||
|
||||
vboxConstraints {
|
||||
marginLeftRight(30.0)
|
||||
marginBottom = 12.0
|
||||
}
|
||||
|
||||
add(ChipTanFlickerCodeView(flickerCodeTanChallenge.flickerCode))
|
||||
}
|
||||
}
|
||||
|
||||
hbox {
|
||||
maxWidth = 400.0
|
||||
|
||||
label(challenge.messageToShowToUser) {
|
||||
isWrapText = true
|
||||
}
|
||||
|
||||
vboxConstraints {
|
||||
marginTopBottom(6.0)
|
||||
}
|
||||
}
|
||||
|
||||
hbox {
|
||||
alignment = Pos.CENTER_LEFT
|
||||
|
||||
label(messages["enter.tan.dialog.enter.tan.label"])
|
||||
|
||||
textfield(enteredTan) {
|
||||
prefHeight = 30.0
|
||||
prefWidth = 110.0
|
||||
|
||||
runLater {
|
||||
requestFocus()
|
||||
}
|
||||
|
||||
hboxConstraints {
|
||||
marginLeft = 6.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hbox {
|
||||
alignment = Pos.CENTER_RIGHT
|
||||
|
||||
button(messages["cancel"]) {
|
||||
prefHeight = ButtonHeight
|
||||
prefWidth = ButtonWidth
|
||||
|
||||
action { cancelledEnteringTan() }
|
||||
|
||||
hboxConstraints {
|
||||
margin = Insets(6.0, 0.0, 4.0, 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
button(messages["ok"]) {
|
||||
prefHeight = ButtonHeight
|
||||
prefWidth = ButtonWidth
|
||||
|
||||
isDefaultButton = true
|
||||
|
||||
action { finishedEnteringTan() }
|
||||
|
||||
hboxConstraints {
|
||||
margin = Insets(6.0, 4.0, 4.0, 12.0)
|
||||
}
|
||||
}
|
||||
|
||||
vboxConstraints {
|
||||
marginTop = 4.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun finishedEnteringTan() {
|
||||
if (enteredTan.value.isNullOrEmpty()) {
|
||||
cancelledEnteringTan()
|
||||
}
|
||||
else {
|
||||
tanEnteredCallback(EnterTanResult.userEnteredTan(enteredTan.value))
|
||||
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelledEnteringTan() {
|
||||
tanEnteredCallback(EnterTanResult.userDidNotEnterTan())
|
||||
|
||||
close()
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package net.dankito.banking.javafx.dialogs.tan.controls
|
||||
|
||||
import javafx.beans.property.SimpleBooleanProperty
|
||||
import javafx.beans.property.SimpleDoubleProperty
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.paint.Color
|
||||
import net.dankito.utils.javafx.ui.extensions.setBackgroundToColor
|
||||
import tornadofx.View
|
||||
import tornadofx.hboxConstraints
|
||||
import tornadofx.label
|
||||
import tornadofx.usePrefWidth
|
||||
|
||||
|
||||
open class ChipTanFlickerCodeStripeView(protected val paintStripeWhite: SimpleBooleanProperty,
|
||||
protected val stripeWidth: SimpleDoubleProperty,
|
||||
protected val stripeHeight: SimpleDoubleProperty,
|
||||
protected val marginRight: SimpleDoubleProperty): View() {
|
||||
|
||||
companion object {
|
||||
val Black = Color.BLACK
|
||||
val White = Color.WHITE
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
paintStripeWhite.addListener { _, _, newValue ->
|
||||
paintStripeWhiteChanged(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override val root = label {
|
||||
prefHeightProperty().bind(stripeHeight)
|
||||
|
||||
usePrefWidth = true
|
||||
prefWidthProperty().bind(stripeWidth)
|
||||
|
||||
marginRight.addListener { _, _, newValue -> setMarginRight(this, newValue.toDouble()) }
|
||||
setMarginRight(this, marginRight.value)
|
||||
}
|
||||
|
||||
private fun setMarginRight(node: Node, rightMargin: Double) {
|
||||
node.hboxConstraints {
|
||||
marginRight = rightMargin
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected open fun paintStripeWhiteChanged(paintWhite: Boolean) {
|
||||
val newColor = if (paintWhite) White else Black
|
||||
|
||||
root.setBackgroundToColor(newColor)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,260 @@
|
|||
package net.dankito.banking.javafx.dialogs.tan.controls
|
||||
|
||||
import javafx.beans.property.SimpleBooleanProperty
|
||||
import javafx.beans.property.SimpleDoubleProperty
|
||||
import javafx.geometry.Pos
|
||||
import javafx.scene.paint.Color
|
||||
import net.dankito.banking.ui.model.tan.FlickerCode
|
||||
import net.dankito.banking.ui.util.FlickerCodeAnimator
|
||||
import net.dankito.fints.tan.Bit
|
||||
import net.dankito.utils.javafx.ui.extensions.fixedHeight
|
||||
import net.dankito.utils.javafx.ui.extensions.fixedWidth
|
||||
import net.dankito.utils.javafx.ui.extensions.setBackgroundToColor
|
||||
import tornadofx.*
|
||||
|
||||
|
||||
class ChipTanFlickerCodeView(private val flickerCode: FlickerCode): View() {
|
||||
|
||||
companion object {
|
||||
private const val ChangeSizeStripeHeightStep = 7.0
|
||||
private const val ChangeSizeStripeWidthStep = 2.0
|
||||
private const val ChangeSizeSpaceBetweenStripesStep = 1.0
|
||||
|
||||
const val MinFlickerCodeViewWidth = 124.0 + ChangeSizeStripeWidthStep + ChangeSizeSpaceBetweenStripesStep // below space between stripes aren't visible anymore
|
||||
const val MaxFlickerCodeViewWidth = 1000.0 // what is a senseful value?
|
||||
|
||||
private const val ChangeFrequencyStep = 5
|
||||
|
||||
private const val IconWidth = 26.0
|
||||
private const val IconHeight = 26.0
|
||||
}
|
||||
|
||||
|
||||
private val flickerCodeLeftRightMargin = SimpleDoubleProperty(31.0)
|
||||
|
||||
private val stripeHeight = SimpleDoubleProperty(127.0)
|
||||
private val stripeWidth = SimpleDoubleProperty(42.0)
|
||||
private val spaceBetweenStripes = SimpleDoubleProperty(10.0)
|
||||
|
||||
private val flickerCodeViewWidth = SimpleDoubleProperty()
|
||||
|
||||
private val stripe1 = SimpleBooleanProperty()
|
||||
private val stripe2 = SimpleBooleanProperty()
|
||||
private val stripe3 = SimpleBooleanProperty()
|
||||
private val stripe4 = SimpleBooleanProperty()
|
||||
private val stripe5 = SimpleBooleanProperty()
|
||||
|
||||
private val isMinSizeReached = SimpleBooleanProperty(false)
|
||||
private val isMaxSizeReached = SimpleBooleanProperty(false)
|
||||
|
||||
private val isMinFrequencyReached = SimpleBooleanProperty(false)
|
||||
private val isMaxFrequencyReached = SimpleBooleanProperty(false)
|
||||
|
||||
private var currentFrequency = 20
|
||||
|
||||
protected val animator = FlickerCodeAnimator()
|
||||
|
||||
|
||||
init {
|
||||
flickerCodeViewWidth.bind(stripeWidth.add(spaceBetweenStripes).multiply(4).add(stripeWidth).add(flickerCodeLeftRightMargin).add(flickerCodeLeftRightMargin))
|
||||
|
||||
animator.setFrequency(currentFrequency)
|
||||
}
|
||||
|
||||
|
||||
override val root = vbox {
|
||||
alignment = Pos.CENTER
|
||||
usePrefHeight = true
|
||||
|
||||
prefWidthProperty().bind(flickerCodeViewWidth)
|
||||
fixedWidth = flickerCodeViewWidth.value
|
||||
|
||||
flickerCodeViewWidth.addListener { _, _, newValue ->
|
||||
fixedWidth = newValue.toDouble()
|
||||
}
|
||||
|
||||
|
||||
hbox {
|
||||
fixedHeight = IconHeight + 6.0
|
||||
|
||||
alignment = Pos.CENTER
|
||||
|
||||
label(messages["enter.tan.dialog.size.label"])
|
||||
|
||||
button("-") {
|
||||
prefHeight = IconHeight
|
||||
prefWidth = IconWidth
|
||||
|
||||
disableWhen(isMinSizeReached)
|
||||
|
||||
action { decreaseSize() }
|
||||
|
||||
hboxConstraints {
|
||||
marginLeft = 6.0
|
||||
marginRight = 4.0
|
||||
}
|
||||
}
|
||||
|
||||
button("+") {
|
||||
prefHeight = IconHeight
|
||||
prefWidth = IconWidth
|
||||
|
||||
disableWhen(isMaxSizeReached)
|
||||
|
||||
action { increaseSize() }
|
||||
}
|
||||
|
||||
label(messages["enter.tan.dialog.frequency.label"]) {
|
||||
hboxConstraints {
|
||||
marginLeft = 12.0
|
||||
}
|
||||
}
|
||||
|
||||
button("-") {
|
||||
prefHeight = IconHeight
|
||||
prefWidth = IconWidth
|
||||
|
||||
disableWhen(isMinFrequencyReached)
|
||||
|
||||
action { decreaseFrequency() }
|
||||
|
||||
hboxConstraints {
|
||||
marginLeft = 6.0
|
||||
marginRight = 4.0
|
||||
}
|
||||
}
|
||||
|
||||
button("+") {
|
||||
prefHeight = IconHeight
|
||||
prefWidth = IconWidth
|
||||
|
||||
disableWhen(isMaxFrequencyReached)
|
||||
|
||||
action { increaseFrequency() }
|
||||
}
|
||||
}
|
||||
|
||||
vbox {
|
||||
setBackgroundToColor(Color.BLACK)
|
||||
|
||||
vbox {
|
||||
anchorpane {
|
||||
|
||||
add(TanGeneratorMarkerView().apply {
|
||||
setLeftMarkerPosition(this)
|
||||
|
||||
stripeWidth.addListener { _, _, _ -> setLeftMarkerPosition(this) }
|
||||
})
|
||||
|
||||
add(TanGeneratorMarkerView().apply {
|
||||
setRightMarkerPosition(this)
|
||||
|
||||
stripeWidth.addListener { _, _, _ -> setRightMarkerPosition(this) }
|
||||
})
|
||||
|
||||
vboxConstraints {
|
||||
marginBottom = 6.0
|
||||
}
|
||||
}
|
||||
|
||||
hbox {
|
||||
minHeight = 150.0
|
||||
|
||||
add(ChipTanFlickerCodeStripeView(stripe1, stripeWidth, stripeHeight, spaceBetweenStripes))
|
||||
add(ChipTanFlickerCodeStripeView(stripe2, stripeWidth, stripeHeight, spaceBetweenStripes))
|
||||
add(ChipTanFlickerCodeStripeView(stripe3, stripeWidth, stripeHeight, spaceBetweenStripes))
|
||||
add(ChipTanFlickerCodeStripeView(stripe4, stripeWidth, stripeHeight, spaceBetweenStripes))
|
||||
add(ChipTanFlickerCodeStripeView(stripe5, stripeWidth, stripeHeight, SimpleDoubleProperty(0.0)))
|
||||
}
|
||||
|
||||
vboxConstraints {
|
||||
marginTopBottom(24.0)
|
||||
marginLeftRight(flickerCodeLeftRightMargin.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
animator.animateFlickerCode(flickerCode) { step ->
|
||||
runLater {
|
||||
paintFlickerCode(step)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun paintFlickerCode(step: Array<Bit>) {
|
||||
stripe1.set(step[0] == Bit.High)
|
||||
stripe2.set(step[1] == Bit.High)
|
||||
stripe3.set(step[2] == Bit.High)
|
||||
stripe4.set(step[3] == Bit.High)
|
||||
stripe5.set(step[4] == Bit.High)
|
||||
}
|
||||
|
||||
private fun setLeftMarkerPosition(component: UIComponent) {
|
||||
component.root.anchorpaneConstraints {
|
||||
leftAnchor = (stripeWidth.value / 2)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setRightMarkerPosition(component: UIComponent) {
|
||||
component.root.anchorpaneConstraints {
|
||||
rightAnchor = (stripeWidth.value / 2)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun increaseSize() {
|
||||
if (isMaxSizeReached.value == false) {
|
||||
stripeHeight.value = stripeHeight.value + ChangeSizeStripeHeightStep
|
||||
stripeWidth.value = stripeWidth.value + ChangeSizeStripeWidthStep
|
||||
spaceBetweenStripes.value = spaceBetweenStripes.value + ChangeSizeSpaceBetweenStripesStep
|
||||
}
|
||||
|
||||
updateMinAndMaxSizeReached()
|
||||
}
|
||||
|
||||
private fun decreaseSize() {
|
||||
if (isMinSizeReached.value == false) {
|
||||
stripeHeight.value = stripeHeight.value - ChangeSizeStripeHeightStep
|
||||
stripeWidth.value = stripeWidth.value - ChangeSizeStripeWidthStep
|
||||
spaceBetweenStripes.value = spaceBetweenStripes.value - ChangeSizeSpaceBetweenStripesStep
|
||||
}
|
||||
|
||||
updateMinAndMaxSizeReached()
|
||||
}
|
||||
|
||||
private fun updateMinAndMaxSizeReached() {
|
||||
val flickerCodeWidth = stripeWidth.value * 5 + spaceBetweenStripes.value * 4
|
||||
|
||||
isMinSizeReached.value = flickerCodeWidth < MinFlickerCodeViewWidth
|
||||
isMaxSizeReached.value = flickerCodeWidth > MaxFlickerCodeViewWidth
|
||||
}
|
||||
|
||||
private fun increaseFrequency() {
|
||||
if (isMaxFrequencyReached.value == false
|
||||
&& (currentFrequency + ChangeFrequencyStep) <= FlickerCodeAnimator.MaxFrequency) {
|
||||
|
||||
currentFrequency += ChangeFrequencyStep
|
||||
animator.setFrequency(currentFrequency)
|
||||
}
|
||||
|
||||
updateMinAndMaxFrequencyReached()
|
||||
}
|
||||
|
||||
private fun decreaseFrequency() {
|
||||
if (isMinFrequencyReached.value == false
|
||||
&& (currentFrequency - ChangeFrequencyStep) >= FlickerCodeAnimator.MinFrequency) {
|
||||
|
||||
currentFrequency -= ChangeFrequencyStep
|
||||
animator.setFrequency(currentFrequency)
|
||||
}
|
||||
|
||||
updateMinAndMaxFrequencyReached()
|
||||
}
|
||||
|
||||
private fun updateMinAndMaxFrequencyReached() {
|
||||
isMaxFrequencyReached.value = (currentFrequency + ChangeFrequencyStep) > FlickerCodeAnimator.MaxFrequency
|
||||
isMinFrequencyReached.value = (currentFrequency - ChangeFrequencyStep) < FlickerCodeAnimator.MinFrequency
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package net.dankito.banking.javafx.dialogs.tan.controls
|
||||
|
||||
import javafx.scene.paint.Color
|
||||
import net.dankito.utils.javafx.ui.extensions.fixedHeight
|
||||
import net.dankito.utils.javafx.ui.extensions.fixedWidth
|
||||
import net.dankito.utils.javafx.ui.extensions.setBackgroundToColor
|
||||
import tornadofx.View
|
||||
import tornadofx.label
|
||||
|
||||
|
||||
class TanGeneratorMarkerView: View() {
|
||||
|
||||
override val root = label {
|
||||
fixedHeight = 20.0
|
||||
|
||||
fixedWidth = 8.0
|
||||
|
||||
setBackgroundToColor(Color.GRAY)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue