Added EnterTanDialog

This commit is contained in:
dankl 2020-01-08 22:32:29 +01:00 committed by dankito
parent fb6fc8688e
commit 9496836b76
10 changed files with 695 additions and 37 deletions

View File

@ -17,4 +17,6 @@ dependencies {
api project(':BankingJavaFxControls') api project(':BankingJavaFxControls')
api project(':fints4javaBankingClient') api project(':fints4javaBankingClient')
implementation "ch.qos.logback:logback-classic:$logbackVersion"
} }

View File

@ -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.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=Online banking password
add.account.dialog.password.hint=The password you use for logging in into online banking 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.error.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.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:

View File

@ -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.customer.id.hint=Der Nutzernamen den Sie fürs Online Banking verwenden
add.account.dialog.password=Online Banking Passwort add.account.dialog.password=Online Banking Passwort
add.account.dialog.password.hint=Das Passwort das Sie fürs Online Banking verwenden 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.error.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.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:

View File

@ -2,14 +2,18 @@ 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.tan.EnterTanDialog
import net.dankito.banking.ui.model.Account import net.dankito.banking.ui.model.Account
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
import net.dankito.banking.ui.model.tan.TanGeneratorTanMedium import net.dankito.banking.ui.model.tan.TanGeneratorTanMedium
import net.dankito.banking.ui.presenter.MainWindowPresenter import net.dankito.banking.ui.presenter.MainWindowPresenter
import tornadofx.FX
import tornadofx.FX.Companion.messages import tornadofx.FX.Companion.messages
import tornadofx.get import tornadofx.get
import java.util.concurrent.CountDownLatch
import java.util.concurrent.atomic.AtomicReference
open class RouterJavaFx : IRouter { open class RouterJavaFx : IRouter {
@ -19,7 +23,19 @@ open class RouterJavaFx : IRouter {
} }
override fun getTanFromUserFromNonUiThread(account: Account, tanChallenge: TanChallenge, presenter: MainWindowPresenter): EnterTanResult { 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 { override fun getAtcFromUserFromNonUiThread(tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult {

View File

@ -4,7 +4,10 @@ import javafx.beans.property.SimpleBooleanProperty
import javafx.beans.property.SimpleStringProperty import javafx.beans.property.SimpleStringProperty
import javafx.geometry.Insets import javafx.geometry.Insets
import javafx.geometry.Pos import javafx.geometry.Pos
import javafx.scene.control.Alert
import javafx.scene.control.ButtonType
import javafx.scene.control.Tooltip import javafx.scene.control.Tooltip
import javafx.scene.layout.Priority
import javafx.scene.paint.Color import javafx.scene.paint.Color
import javafx.scene.text.Font import javafx.scene.text.Font
import javafx.scene.text.FontWeight 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 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) protected val isEnteredCredentialsResultVisible = SimpleBooleanProperty(false)
private val didEnteredCredentialsMatch = SimpleBooleanProperty(false)
private val checkCredentialsButton = UpdateButton(messages["check"]) protected val checkCredentialsButton = UpdateButton(messages["check"])
init { init {
@ -120,30 +124,27 @@ open class AddAccountDialog(protected val presenter: MainWindowPresenter) : Wind
} }
label(checkEnteredCredentialsResult) { label(checkEnteredCredentialsResult) {
useMaxHeight = true
visibleWhen(isEnteredCredentialsResultVisible) visibleWhen(isEnteredCredentialsResultVisible)
ensureOnlyUsesSpaceIfVisible() ensureOnlyUsesSpaceIfVisible()
isWrapText = true isWrapText = true
font = Font(font.size + 1) font = Font(font.size + 1)
setBackgroundToColor(Color.RED)
paddingAll = 8.0 paddingAll = 8.0
checkEnteredCredentialsResult.addListener { _, _, _ -> checkEnteredCredentialsResult.addListener { _, _, _ ->
if (didEnteredCredentialsMatch.value) {
setBackgroundToColor(Color.TRANSPARENT)
}
else {
setBackgroundToColor(Color.RED)
}
tooltip = Tooltip(checkEnteredCredentialsResult.value) tooltip = Tooltip(checkEnteredCredentialsResult.value)
currentWindow?.sizeToScene()
} }
vboxConstraints { vboxConstraints {
marginTop = 12.0 marginTop = 12.0
marginBottom = 6.0 marginBottom = 6.0
vGrow = Priority.ALWAYS
} }
} }
@ -151,7 +152,8 @@ open class AddAccountDialog(protected val presenter: MainWindowPresenter) : Wind
alignment = Pos.CENTER_RIGHT alignment = Pos.CENTER_RIGHT
button(messages["cancel"]) { button(messages["cancel"]) {
prefHeight = ButtonHeight minHeight = ButtonHeight
maxHeight = ButtonHeight
prefWidth = ButtonWidth prefWidth = ButtonWidth
isCancelButton = true isCancelButton = true
@ -164,7 +166,8 @@ open class AddAccountDialog(protected val presenter: MainWindowPresenter) : Wind
} }
add(checkCredentialsButton.apply { add(checkCredentialsButton.apply {
prefHeight = ButtonHeight minHeight = ButtonHeight
maxHeight = ButtonHeight
prefWidth = ButtonWidth prefWidth = ButtonWidth
isDefaultButton = true isDefaultButton = true
@ -203,6 +206,8 @@ open class AddAccountDialog(protected val presenter: MainWindowPresenter) : Wind
protected open fun checkEnteredCredentials() { protected open fun checkEnteredCredentials() {
isEnteredCredentialsResultVisible.value = false
selectedBank?.let { selectedBank?.let {
presenter.addAccountAsync(it, customerId.value, password.value) { response -> presenter.addAccountAsync(it, customerId.value, password.value) { response ->
runLater { handleAddAccountResultOnUiThread(response) } runLater { handleAddAccountResultOnUiThread(response) }
@ -211,21 +216,33 @@ open class AddAccountDialog(protected val presenter: MainWindowPresenter) : Wind
} }
protected open fun handleAddAccountResultOnUiThread(response: AddAccountResponse) { protected open fun handleAddAccountResultOnUiThread(response: AddAccountResponse) {
isEnteredCredentialsResultVisible.value = true checkCredentialsButton.resetIsUpdating()
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
if (response.isSuccessful) { 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()
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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