diff --git a/BankingJavaFxApp/build.gradle b/BankingJavaFxApp/build.gradle index 600c78bf..c3ede5ad 100644 --- a/BankingJavaFxApp/build.gradle +++ b/BankingJavaFxApp/build.gradle @@ -17,4 +17,6 @@ dependencies { api project(':BankingJavaFxControls') api project(':fints4javaBankingClient') + + implementation "ch.qos.logback:logback-classic:$logbackVersion" } \ No newline at end of file diff --git a/BankingJavaFxApp/src/main/resources/Messages.properties b/BankingJavaFxApp/src/main/resources/Messages.properties index 80237ac2..cfb8e637 100755 --- a/BankingJavaFxApp/src/main/resources/Messages.properties +++ b/BankingJavaFxApp/src/main/resources/Messages.properties @@ -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 \ No newline at end of file +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: \ No newline at end of file diff --git a/BankingJavaFxApp/src/main/resources/Messages_de.properties b/BankingJavaFxApp/src/main/resources/Messages_de.properties index 9f48d322..65096090 100755 --- a/BankingJavaFxApp/src/main/resources/Messages_de.properties +++ b/BankingJavaFxApp/src/main/resources/Messages_de.properties @@ -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. \ No newline at end of file +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: \ No newline at end of file diff --git a/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/RouterJavaFx.kt b/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/RouterJavaFx.kt index e5c15021..f0e7a341 100644 --- a/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/RouterJavaFx.kt +++ b/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/RouterJavaFx.kt @@ -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(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 { diff --git a/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/AddAccountDialog.kt b/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/AddAccountDialog.kt index ad568fda..2907446a 100755 --- a/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/AddAccountDialog.kt +++ b/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/AddAccountDialog.kt @@ -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() + } + } \ No newline at end of file diff --git a/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/JavaFxDialogService.kt b/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/JavaFxDialogService.kt new file mode 100755 index 00000000..f6c3e107 --- /dev/null +++ b/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/JavaFxDialogService.kt @@ -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 + } + +} \ No newline at end of file diff --git a/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/tan/EnterTanDialog.kt b/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/tan/EnterTanDialog.kt new file mode 100644 index 00000000..4afa5325 --- /dev/null +++ b/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/tan/EnterTanDialog.kt @@ -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() + } + +} \ No newline at end of file diff --git a/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/tan/controls/ChipTanFlickerCodeStripeView.kt b/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/tan/controls/ChipTanFlickerCodeStripeView.kt new file mode 100644 index 00000000..c3da8939 --- /dev/null +++ b/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/tan/controls/ChipTanFlickerCodeStripeView.kt @@ -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) + } + +} \ No newline at end of file diff --git a/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/tan/controls/ChipTanFlickerCodeView.kt b/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/tan/controls/ChipTanFlickerCodeView.kt new file mode 100644 index 00000000..f4111bf1 --- /dev/null +++ b/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/tan/controls/ChipTanFlickerCodeView.kt @@ -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) { + 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 + } + +} \ No newline at end of file diff --git a/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/tan/controls/TanGeneratorMarkerView.kt b/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/tan/controls/TanGeneratorMarkerView.kt new file mode 100644 index 00000000..7308a353 --- /dev/null +++ b/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/tan/controls/TanGeneratorMarkerView.kt @@ -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) + } + +} \ No newline at end of file