From 56370d6533bd707206d537a18cff52a26dc46ccb Mon Sep 17 00:00:00 2001 From: itorod Date: Sat, 23 Mar 2024 00:50:12 +0000 Subject: [PATCH 1/3] changed helpLabel code to errorLabel code for error validation and allow resize of component when error is shown. Also bring back helptext when error disappears changed helpLabel code to errorLabel code for error validation made helplabel invisible when error label shows removed commented code. Setting errorlabel to help is unecessary changed spacing of vbox to accomodate error message removed setopacity and used setmanage instead. Made height to be calculated automatically for error text removed unecessary commented code enabled reformat code added code to make height go back to original position after resize Check that error style does not exist before you set error style --- .../controls/MaterialTextField.java | 64 ++++++++++++++++--- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/MaterialTextField.java b/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/MaterialTextField.java index 23ff2a8f0c..0844691879 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/MaterialTextField.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/MaterialTextField.java @@ -57,8 +57,11 @@ public class MaterialTextField extends Pane { @Getter protected final Label helpLabel = new Label(); @Getter + protected final Label errorLabel = new Label(); + @Getter private final BisqIconButton iconButton = new BisqIconButton(); private ChangeListener iconButtonHeightListener; + private String globalHelpText = ""; public MaterialTextField() { this(null, null, null); @@ -116,9 +119,13 @@ public MaterialTextField(@Nullable String description, @Nullable String prompt, helpLabel.setMouseTransparent(true); if (StringUtils.isNotEmpty(help)) { helpLabel.setText(help); + globalHelpText = help; } - getChildren().addAll(bg, line, selectionLine, descriptionLabel, textInputControl, iconButton, helpLabel); + errorLabel.setLayoutX(16); + errorLabel.setMouseTransparent(true); + + getChildren().addAll(bg, line, selectionLine, descriptionLabel, textInputControl, iconButton, helpLabel, errorLabel); widthProperty().addListener(new WeakReference>((observable, oldValue, newValue) -> onWidthChanged((double) newValue)).get()); @@ -127,7 +134,6 @@ public MaterialTextField(@Nullable String description, @Nullable String prompt, onInputTextFieldFocus(newValue)).get()); descriptionLabel.textProperty().addListener(new WeakReference>((observable, oldValue, newValue) -> update()).get()); - promptTextProperty().addListener(new WeakReference>((observable, oldValue, newValue) -> update()).get()); helpProperty().addListener(new WeakReference>((observable, oldValue, newValue) -> @@ -175,18 +181,23 @@ public void setValidators(ValidatorBase... validators) { validationControl.setValidators(validators); } - // TODO add custom errorLabel and not reuse helpLabel as it would cause an exception when binding at the helpLabel is used public boolean validate() { isValid.set(validationControl.validate()); selectionLine.pseudoClassStateChanged(PSEUDO_CLASS_ERROR, !isValid.get()); descriptionLabel.pseudoClassStateChanged(PSEUDO_CLASS_ERROR, !isValid.get()); getActiveValidator().ifPresentOrElse(validator -> { - helpLabel.setText(validator.getMessage()); - helpLabel.pseudoClassStateChanged(PSEUDO_CLASS_ERROR, true); + errorLabel.setText(validator.getMessage()); + errorLabel.pseudoClassStateChanged(PSEUDO_CLASS_ERROR, true); + helpLabel.setManaged(false); + helpLabel.setVisible(false); + helpLabel.setText(""); }, () -> { - helpLabel.setText(""); - helpLabel.pseudoClassStateChanged(PSEUDO_CLASS_ERROR, false); + errorLabel.setText(""); + errorLabel.pseudoClassStateChanged(PSEUDO_CLASS_ERROR, false); + helpLabel.setManaged(true); + helpLabel.setVisible(true); + helpLabel.setText(globalHelpText); }); return isValid.get(); } @@ -196,8 +207,8 @@ public void resetValidation() { isValid.set(false); selectionLine.pseudoClassStateChanged(PSEUDO_CLASS_ERROR, false); descriptionLabel.pseudoClassStateChanged(PSEUDO_CLASS_ERROR, false); - helpLabel.pseudoClassStateChanged(PSEUDO_CLASS_ERROR, false); - helpLabel.setText(""); + errorLabel.pseudoClassStateChanged(PSEUDO_CLASS_ERROR, false); + errorLabel.setText(""); } private final BooleanProperty isValid = new SimpleBooleanProperty(); @@ -294,6 +305,22 @@ public final StringProperty helpProperty() { return helpLabel.textProperty(); } + /////////////////////////////////////////////////////////////////////////////////////////////////// + // Error + /////////////////////////////////////////////////////////////////////////////////////////////////// + + public String getErrorText() { + return errorLabel.getText(); + } + + public void setErrorText(String value) { + errorLabel.setText(value); + } + + public final StringProperty errorProperty() { + return errorLabel.textProperty(); + } + /////////////////////////////////////////////////////////////////////////////////////////////////// // Icon @@ -406,6 +433,7 @@ protected void onWidthChanged(double width) { double iconWidth = iconButton.isVisible() ? 25 : 0; textInputControl.setPrefWidth(width - 2 * textInputControl.getLayoutX() - iconWidth); helpLabel.setPrefWidth(width - 2 * helpLabel.getLayoutX()); + errorLabel.setPrefWidth(width - 2 * errorLabel.getLayoutX()); } } @@ -432,6 +460,7 @@ protected void doLayout() { selectionLine.setLayoutY(getBgHeight() - 2); textInputControl.setLayoutY(getFieldLayoutY()); helpLabel.setLayoutY(getBgHeight() + 3.5); + errorLabel.setLayoutY(getBgHeight() + 3.5); } private void layoutIconButton() { @@ -465,6 +494,7 @@ void update() { Transitions.animateLayoutY(descriptionLabel, 16.5, Transitions.DEFAULT_DURATION / 6d, null); } } + helpLabel.setVisible(StringUtils.isNotEmpty(helpProperty().get())); helpLabel.setManaged(StringUtils.isNotEmpty(helpProperty().get())); @@ -477,6 +507,16 @@ void update() { descriptionLabel.getStyleClass().remove("material-text-field-description-deselected"); descriptionLabel.getStyleClass().remove("material-text-field-description-read-only"); + if (showErrorLabel()) { + if (!errorLabel.getStyleClass().contains("material-text-field-help")) + errorLabel.getStyleClass().add("material-text-field-help"); + errorLabel.setVisible(true); + errorLabel.setManaged(true); + } else { + errorLabel.setVisible(false); + errorLabel.setManaged(false); + } + if (showInputTextField()) { descriptionLabel.getStyleClass().add("material-text-field-description-small"); } else { @@ -517,6 +557,10 @@ protected boolean showInputTextField() { textInputControl.isFocused(); } + protected boolean showErrorLabel() { + return StringUtils.isNotEmpty(errorProperty().get()); + } + protected double getBgHeight() { return 56; } @@ -529,6 +573,8 @@ protected double getFieldLayoutY() { protected double computeMinHeight(double width) { if (helpLabel.isManaged()) { return helpLabel.getLayoutY() + helpLabel.getHeight(); + } else if (errorLabel.isManaged()) { + return errorLabel.getLayoutY() + errorLabel.getHeight(); } else { return getBgHeight(); } From 5f018d519d44857501763b664f34330e468d4399 Mon Sep 17 00:00:00 2001 From: HenrikJannsen Date: Fri, 5 Apr 2024 00:01:22 +0700 Subject: [PATCH 2/3] Improve input validation --- .../controls/MaterialTextField.java | 58 +++++++------------ .../controls/validator/NumberValidator.java | 16 ++++- .../security_manager/SecurityManagerView.java | 4 +- .../desktop/src/main/resources/css/text.css | 19 +++--- 4 files changed, 47 insertions(+), 50 deletions(-) diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/MaterialTextField.java b/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/MaterialTextField.java index 0844691879..afc512b537 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/MaterialTextField.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/MaterialTextField.java @@ -61,7 +61,6 @@ public class MaterialTextField extends Pane { @Getter private final BisqIconButton iconButton = new BisqIconButton(); private ChangeListener iconButtonHeightListener; - private String globalHelpText = ""; public MaterialTextField() { this(null, null, null); @@ -93,7 +92,6 @@ public MaterialTextField(@Nullable String description, @Nullable String prompt, descriptionLabel.setLayoutX(16); descriptionLabel.setMouseTransparent(true); - // descriptionLabel.setStyle("-fx-font-family: \"IBM Plex Sans Light\";"); if (StringUtils.isNotEmpty(description)) { descriptionLabel.setText(description); } @@ -119,11 +117,13 @@ public MaterialTextField(@Nullable String description, @Nullable String prompt, helpLabel.setMouseTransparent(true); if (StringUtils.isNotEmpty(help)) { helpLabel.setText(help); - globalHelpText = help; } errorLabel.setLayoutX(16); errorLabel.setMouseTransparent(true); + errorLabel.getStyleClass().add("material-text-field-error"); + errorLabel.setManaged(false); + errorLabel.setVisible(false); getChildren().addAll(bg, line, selectionLine, descriptionLabel, textInputControl, iconButton, helpLabel, errorLabel); @@ -182,24 +182,17 @@ public void setValidators(ValidatorBase... validators) { } public boolean validate() { - isValid.set(validationControl.validate()); - selectionLine.pseudoClassStateChanged(PSEUDO_CLASS_ERROR, !isValid.get()); - descriptionLabel.pseudoClassStateChanged(PSEUDO_CLASS_ERROR, !isValid.get()); - getActiveValidator().ifPresentOrElse(validator -> { - errorLabel.setText(validator.getMessage()); - errorLabel.pseudoClassStateChanged(PSEUDO_CLASS_ERROR, true); - helpLabel.setManaged(false); - helpLabel.setVisible(false); - helpLabel.setText(""); - }, - () -> { - errorLabel.setText(""); - errorLabel.pseudoClassStateChanged(PSEUDO_CLASS_ERROR, false); - helpLabel.setManaged(true); - helpLabel.setVisible(true); - helpLabel.setText(globalHelpText); - }); - return isValid.get(); + resetValidation(); + boolean valid = validationControl.validate(); + isValid.set(valid); + selectionLine.pseudoClassStateChanged(PSEUDO_CLASS_ERROR, !valid); + descriptionLabel.pseudoClassStateChanged(PSEUDO_CLASS_ERROR, !valid); + errorLabel.setVisible(!valid); + errorLabel.setManaged(errorLabel.isVisible()); + Optional activeValidator = getActiveValidator(); + errorLabel.setText(activeValidator.map(ValidatorBase::getMessage).orElse("")); + update(); + return valid; } public void resetValidation() { @@ -207,7 +200,6 @@ public void resetValidation() { isValid.set(false); selectionLine.pseudoClassStateChanged(PSEUDO_CLASS_ERROR, false); descriptionLabel.pseudoClassStateChanged(PSEUDO_CLASS_ERROR, false); - errorLabel.pseudoClassStateChanged(PSEUDO_CLASS_ERROR, false); errorLabel.setText(""); } @@ -216,6 +208,8 @@ public void resetValidation() { public BooleanProperty isValidProperty() { return isValid; } + + /////////////////////////////////////////////////////////////////////////////////////////////////// // Description /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -495,8 +489,8 @@ void update() { } } - helpLabel.setVisible(StringUtils.isNotEmpty(helpProperty().get())); - helpLabel.setManaged(StringUtils.isNotEmpty(helpProperty().get())); + helpLabel.setVisible(isValid.get() && StringUtils.isNotEmpty(getHelpText())); + helpLabel.setManaged(helpLabel.isVisible()); descriptionLabel.getStyleClass().remove("material-text-field-description-read-only"); textInputControl.getStyleClass().remove("material-text-field-read-only"); @@ -507,16 +501,6 @@ void update() { descriptionLabel.getStyleClass().remove("material-text-field-description-deselected"); descriptionLabel.getStyleClass().remove("material-text-field-description-read-only"); - if (showErrorLabel()) { - if (!errorLabel.getStyleClass().contains("material-text-field-help")) - errorLabel.getStyleClass().add("material-text-field-help"); - errorLabel.setVisible(true); - errorLabel.setManaged(true); - } else { - errorLabel.setVisible(false); - errorLabel.setManaged(false); - } - if (showInputTextField()) { descriptionLabel.getStyleClass().add("material-text-field-description-small"); } else { @@ -541,8 +525,10 @@ void update() { descriptionLabel.getStyleClass().add("material-text-field-description-read-only"); textInputControl.getStyleClass().add("material-text-field-read-only"); } + setOpacity(textInputControl.isDisabled() ? 0.35 : 1); UIThread.runOnNextRenderFrame(this::layoutIconButton); + layout(); } protected void removeBgStyles() { @@ -557,10 +543,6 @@ protected boolean showInputTextField() { textInputControl.isFocused(); } - protected boolean showErrorLabel() { - return StringUtils.isNotEmpty(errorProperty().get()); - } - protected double getBgHeight() { return 56; } diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/validator/NumberValidator.java b/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/validator/NumberValidator.java index 60a5f9dc4e..bac40df12e 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/validator/NumberValidator.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/validator/NumberValidator.java @@ -17,6 +17,7 @@ package bisq.desktop.components.controls.validator; +import bisq.common.util.StringUtils; import javafx.scene.control.TextInputControl; import javafx.util.StringConverter; import javafx.util.converter.NumberStringConverter; @@ -33,15 +34,22 @@ public class NumberValidator extends ValidatorBase { private Optional maxValue = Optional.empty(); @Getter private Optional numberValue = Optional.empty(); + private final boolean allowEmptyString; public NumberValidator(String message) { super(message); + this.allowEmptyString = false; } public NumberValidator(String message, Number minValue, Number maxValue) { + this(message, minValue, maxValue, true); + } + + public NumberValidator(String message, Number minValue, Number maxValue, boolean allowEmptyString) { super(message); this.minValue = Optional.of(minValue); this.maxValue = Optional.of(maxValue); + this.allowEmptyString = allowEmptyString; } public void setMinValue(Number minValue) { @@ -54,9 +62,15 @@ public void setMaxValue(Number maxValue) { @Override protected void eval() { + hasErrors.set(false); var textField = (TextInputControl) srcControl.get(); try { - Number value = NUMBER_STRING_CONVERTER.fromString(textField.getText()); + String text = textField.getText(); + if (allowEmptyString && StringUtils.isEmpty(text)) { + return; + } + + Number value = NUMBER_STRING_CONVERTER.fromString(text); numberValue = Optional.of(value); if (minValue.isPresent()) { diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/security_manager/SecurityManagerView.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/security_manager/SecurityManagerView.java index 595c86b155..62ca54ba4b 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/security_manager/SecurityManagerView.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/security_manager/SecurityManagerView.java @@ -61,10 +61,10 @@ public class SecurityManagerView extends View { private static final ValidatorBase DIFFICULTY_ADJUSTMENT_FACTOR_VALIDATOR = new NumberValidator(Res.get("authorizedRole.securityManager.difficultyAdjustment.invalid", NetworkLoad.MAX_DIFFICULTY_ADJUSTMENT), - 0, NetworkLoad.MAX_DIFFICULTY_ADJUSTMENT); + 0, NetworkLoad.MAX_DIFFICULTY_ADJUSTMENT, false); private static final ValidatorBase MIN_REPUTATION_SCORE_VALIDATOR = new NumberValidator(Res.get("authorizedRole.securityManager.minRequiredReputationScore.invalid", ReputationScore.MAX_VALUE), - 0, ReputationScore.MAX_VALUE); + 0, ReputationScore.MAX_VALUE, false); private final Button difficultyAdjustmentButton, minRequiredReputationScoreButton, sendAlertButton; private final MaterialTextArea message; diff --git a/apps/desktop/desktop/src/main/resources/css/text.css b/apps/desktop/desktop/src/main/resources/css/text.css index b46535de40..81d9225e53 100644 --- a/apps/desktop/desktop/src/main/resources/css/text.css +++ b/apps/desktop/desktop/src/main/resources/css/text.css @@ -590,10 +590,6 @@ -fx-background-color: -bisq2-green; } -.material-text-field-selection-line:error { - -fx-background-color: -bisq2-red; -} - .material-text-field-bg { -fx-background-color: rgba(255, 255, 255, 0.05); -fx-background-radius: 4 4 0 0; @@ -619,10 +615,6 @@ -fx-text-fill: -bisq2-green; } -.material-text-field-description-selected:error { - -fx-text-fill: -bisq2-red; -} - .material-text-field-description-deselected { -fx-text-fill: -fx-mid-text-color; } @@ -641,7 +633,16 @@ -fx-font-family: "IBM Plex Sans Light"; } -.material-text-field-help:error { +.material-text-field-selection-line:error, +.material-text-field-description-deselected:error, +.material-text-field-description-read-only:error, +.material-text-field-description-selected:error, +.material-text-field-description-small:error, +.material-text-field-description-big:error { + -fx-text-fill: -bisq2-red; +} + +.material-text-field-error { -fx-font-size: 0.95em; -fx-text-fill: -bisq2-red; -fx-font-family: "IBM Plex Sans Light"; From e345c0fd6206208c91d7c0e23fa89b492ea2b1ab Mon Sep 17 00:00:00 2001 From: HenrikJannsen Date: Fri, 5 Apr 2024 01:38:07 +0700 Subject: [PATCH 3/3] Remove public API for errorLabel as it is used only by internal validator. --- .../components/controls/MaterialTextField.java | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/MaterialTextField.java b/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/MaterialTextField.java index afc512b537..db7c8e4193 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/MaterialTextField.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/MaterialTextField.java @@ -299,22 +299,6 @@ public final StringProperty helpProperty() { return helpLabel.textProperty(); } - /////////////////////////////////////////////////////////////////////////////////////////////////// - // Error - /////////////////////////////////////////////////////////////////////////////////////////////////// - - public String getErrorText() { - return errorLabel.getText(); - } - - public void setErrorText(String value) { - errorLabel.setText(value); - } - - public final StringProperty errorProperty() { - return errorLabel.textProperty(); - } - /////////////////////////////////////////////////////////////////////////////////////////////////// // Icon