mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-07-01 10:07:02 -04:00
changed validation
This commit is contained in:
parent
ed5cc4d628
commit
b91fe8273b
14 changed files with 320 additions and 365 deletions
|
@ -22,7 +22,6 @@ import io.bitsquare.btc.BlockChainFacade;
|
||||||
import io.bitsquare.btc.FeePolicy;
|
import io.bitsquare.btc.FeePolicy;
|
||||||
import io.bitsquare.btc.WalletFacade;
|
import io.bitsquare.btc.WalletFacade;
|
||||||
import io.bitsquare.crypto.CryptoFacade;
|
import io.bitsquare.crypto.CryptoFacade;
|
||||||
import io.bitsquare.gui.util.BSFormatter;
|
|
||||||
import io.bitsquare.msg.BootstrappedPeerFactory;
|
import io.bitsquare.msg.BootstrappedPeerFactory;
|
||||||
import io.bitsquare.msg.MessageFacade;
|
import io.bitsquare.msg.MessageFacade;
|
||||||
import io.bitsquare.msg.P2PNode;
|
import io.bitsquare.msg.P2PNode;
|
||||||
|
@ -64,7 +63,6 @@ public class BitSquareModule extends AbstractModule {
|
||||||
bind(BootstrappedPeerFactory.class).asEagerSingleton();
|
bind(BootstrappedPeerFactory.class).asEagerSingleton();
|
||||||
|
|
||||||
bind(TradeManager.class).asEagerSingleton();
|
bind(TradeManager.class).asEagerSingleton();
|
||||||
bind(BSFormatter.class).asEagerSingleton();
|
|
||||||
|
|
||||||
|
|
||||||
//bind(String.class).annotatedWith(Names.named("networkType")).toInstance(WalletFacade.MAIN_NET);
|
//bind(String.class).annotatedWith(Names.named("networkType")).toInstance(WalletFacade.MAIN_NET);
|
||||||
|
|
|
@ -19,8 +19,8 @@ package io.bitsquare.gui.components;
|
||||||
|
|
||||||
import io.bitsquare.gui.util.validation.InputValidator;
|
import io.bitsquare.gui.util.validation.InputValidator;
|
||||||
|
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Point2D;
|
import javafx.geometry.Point2D;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
|
@ -43,15 +43,14 @@ import org.slf4j.LoggerFactory;
|
||||||
*/
|
*/
|
||||||
public class ValidatingTextField extends TextField {
|
public class ValidatingTextField extends TextField {
|
||||||
private static final Logger log = LoggerFactory.getLogger(ValidatingTextField.class);
|
private static final Logger log = LoggerFactory.getLogger(ValidatingTextField.class);
|
||||||
private static PopOver popOver;
|
|
||||||
|
|
||||||
private Effect invalidEffect = new DropShadow(BlurType.GAUSSIAN, Color.RED, 4, 0.0, 0, 0);
|
private Effect invalidEffect = new DropShadow(BlurType.GAUSSIAN, Color.RED, 4, 0.0, 0, 0);
|
||||||
|
|
||||||
private final BooleanProperty isValid = new SimpleBooleanProperty(true);
|
final ObjectProperty<InputValidator.ValidationResult> amountValidationResult = new SimpleObjectProperty<>(new
|
||||||
private InputValidator validator;
|
InputValidator.ValidationResult(true));
|
||||||
private boolean validateOnFocusOut = true;
|
|
||||||
private boolean needsValidationOnFocusOut;
|
private static PopOver popOver;
|
||||||
private Region errorPopupLayoutReference;
|
private Region errorPopupLayoutReference = this;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -62,15 +61,31 @@ public class ValidatingTextField extends TextField {
|
||||||
if (popOver != null)
|
if (popOver != null)
|
||||||
popOver.hide();
|
popOver.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Constructor
|
// Constructor
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public ValidatingTextField() {
|
public ValidatingTextField() {
|
||||||
super();
|
super();
|
||||||
setupListeners();
|
|
||||||
|
amountValidationResult.addListener((ov, oldValue, newValue) -> {
|
||||||
|
if (newValue != null) {
|
||||||
|
setEffect(newValue.isValid ? null : invalidEffect);
|
||||||
|
|
||||||
|
if (newValue.isValid)
|
||||||
|
hidePopover();
|
||||||
|
else
|
||||||
|
applyErrorMessage(newValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sceneProperty().addListener((ov, oldValue, newValue) -> {
|
||||||
|
// we got removed from the scene
|
||||||
|
// lets hide an open popup
|
||||||
|
if (newValue == null)
|
||||||
|
hidePopover();
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -78,21 +93,14 @@ public class ValidatingTextField extends TextField {
|
||||||
// Public methods
|
// Public methods
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void reValidate() {
|
|
||||||
validate(getText());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Setters
|
// Setters
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void setValidator(InputValidator validator) {
|
|
||||||
this.validator = validator;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param errorPopupLayoutReference The node used as reference for positioning
|
* @param errorPopupLayoutReference The node used as reference for positioning. If not set explicitely the
|
||||||
|
* ValidatingTextField instance is used.
|
||||||
*/
|
*/
|
||||||
public void setErrorPopupLayoutReference(Region errorPopupLayoutReference) {
|
public void setErrorPopupLayoutReference(Region errorPopupLayoutReference) {
|
||||||
this.errorPopupLayoutReference = errorPopupLayoutReference;
|
this.errorPopupLayoutReference = errorPopupLayoutReference;
|
||||||
|
@ -103,12 +111,8 @@ public class ValidatingTextField extends TextField {
|
||||||
// Getters
|
// Getters
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public boolean getIsValid() {
|
public ObjectProperty<InputValidator.ValidationResult> amountValidationResultProperty() {
|
||||||
return isValid.get();
|
return amountValidationResult;
|
||||||
}
|
|
||||||
|
|
||||||
public BooleanProperty isValidProperty() {
|
|
||||||
return isValid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -116,40 +120,6 @@ public class ValidatingTextField extends TextField {
|
||||||
// Private methods
|
// Private methods
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private void setupListeners() {
|
|
||||||
sceneProperty().addListener((ov, oldValue, newValue) -> {
|
|
||||||
// we got removed from the scene
|
|
||||||
// lets hide an open popup
|
|
||||||
if (newValue == null)
|
|
||||||
hidePopover();
|
|
||||||
});
|
|
||||||
|
|
||||||
textProperty().addListener((ov, oldValue, newValue) -> {
|
|
||||||
if (validator != null) {
|
|
||||||
if (!validateOnFocusOut)
|
|
||||||
validate(newValue);
|
|
||||||
else
|
|
||||||
needsValidationOnFocusOut = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
focusedProperty().addListener((ov, oldValue, newValue) -> {
|
|
||||||
if (validateOnFocusOut && needsValidationOnFocusOut &&
|
|
||||||
!newValue && getScene() != null && getScene().getWindow().isFocused())
|
|
||||||
validate(getText());
|
|
||||||
});
|
|
||||||
|
|
||||||
isValid.addListener((ov, oldValue, newValue) -> applyEffect(newValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validate(String input) {
|
|
||||||
if (input != null && validator != null) {
|
|
||||||
InputValidator.ValidationResult validationResult = validator.validate(input);
|
|
||||||
isValid.set(validationResult.isValid);
|
|
||||||
applyErrorMessage(validationResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyErrorMessage(InputValidator.ValidationResult validationResult) {
|
private void applyErrorMessage(InputValidator.ValidationResult validationResult) {
|
||||||
if (validationResult.isValid) {
|
if (validationResult.isValid) {
|
||||||
if (popOver != null) {
|
if (popOver != null) {
|
||||||
|
@ -160,35 +130,21 @@ public class ValidatingTextField extends TextField {
|
||||||
if (popOver == null)
|
if (popOver == null)
|
||||||
createErrorPopOver(validationResult.errorMessage);
|
createErrorPopOver(validationResult.errorMessage);
|
||||||
else
|
else
|
||||||
setErrorMessage(validationResult.errorMessage);
|
((Label) popOver.getContentNode()).setText(validationResult.errorMessage);
|
||||||
|
|
||||||
popOver.show(getScene().getWindow(), getErrorPopupPosition().getX(), getErrorPopupPosition().getY());
|
popOver.show(getScene().getWindow(), getErrorPopupPosition().getX(), getErrorPopupPosition().getY());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyEffect(boolean isValid) {
|
|
||||||
setEffect(isValid ? null : invalidEffect);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Point2D getErrorPopupPosition() {
|
private Point2D getErrorPopupPosition() {
|
||||||
Window window = getScene().getWindow();
|
Window window = getScene().getWindow();
|
||||||
Point2D point;
|
Point2D point;
|
||||||
double x;
|
point = errorPopupLayoutReference.localToScene(0, 0);
|
||||||
if (errorPopupLayoutReference == null) {
|
double x = point.getX() + window.getX() + errorPopupLayoutReference.getWidth() + 20;
|
||||||
point = localToScene(0, 0);
|
|
||||||
x = point.getX() + window.getX() + getWidth() + 20;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
point = errorPopupLayoutReference.localToScene(0, 0);
|
|
||||||
x = point.getX() + window.getX() + errorPopupLayoutReference.getWidth() + 20;
|
|
||||||
}
|
|
||||||
double y = point.getY() + window.getY() + Math.floor(getHeight() / 2);
|
double y = point.getY() + window.getY() + Math.floor(getHeight() / 2);
|
||||||
return new Point2D(x, y);
|
return new Point2D(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setErrorMessage(String errorMessage) {
|
|
||||||
((Label) popOver.getContentNode()).setText(errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void createErrorPopOver(String errorMessage) {
|
private static void createErrorPopOver(String errorMessage) {
|
||||||
Label errorLabel = new Label(errorMessage);
|
Label errorLabel = new Label(errorMessage);
|
||||||
|
@ -196,8 +152,8 @@ public class ValidatingTextField extends TextField {
|
||||||
errorLabel.setPadding(new Insets(0, 10, 0, 10));
|
errorLabel.setPadding(new Insets(0, 10, 0, 10));
|
||||||
|
|
||||||
popOver = new PopOver(errorLabel);
|
popOver = new PopOver(errorLabel);
|
||||||
popOver.setAutoFix(true);
|
popOver.setDetachable(false);
|
||||||
popOver.setDetachedTitle("");
|
|
||||||
popOver.setArrowIndent(5);
|
popOver.setArrowIndent(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -75,6 +75,8 @@ public class TradeController extends CachedViewController {
|
||||||
|
|
||||||
// TODO find better solution
|
// TODO find better solution
|
||||||
// Textfield focus out triggers validation, use runLater as quick fix...
|
// Textfield focus out triggers validation, use runLater as quick fix...
|
||||||
|
|
||||||
|
//TODO update to new verison
|
||||||
((TabPane) root).getSelectionModel().selectedIndexProperty().addListener((observableValue) ->
|
((TabPane) root).getSelectionModel().selectedIndexProperty().addListener((observableValue) ->
|
||||||
Platform.runLater(ValidatingTextField::hidePopover));
|
Platform.runLater(ValidatingTextField::hidePopover));
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,9 +24,6 @@ import io.bitsquare.gui.components.btc.AddressTextField;
|
||||||
import io.bitsquare.gui.components.btc.BalanceTextField;
|
import io.bitsquare.gui.components.btc.BalanceTextField;
|
||||||
import io.bitsquare.gui.components.confidence.ConfidenceProgressIndicator;
|
import io.bitsquare.gui.components.confidence.ConfidenceProgressIndicator;
|
||||||
import io.bitsquare.gui.trade.TradeController;
|
import io.bitsquare.gui.trade.TradeController;
|
||||||
import io.bitsquare.gui.util.validation.BtcValidator;
|
|
||||||
import io.bitsquare.gui.util.validation.FiatValidator;
|
|
||||||
import io.bitsquare.gui.util.validation.ValidationHelper;
|
|
||||||
import io.bitsquare.trade.orderbook.OrderBookFilter;
|
import io.bitsquare.trade.orderbook.OrderBookFilter;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
@ -78,8 +75,12 @@ public class CreateOfferCB extends CachedViewController {
|
||||||
public void initialize(URL url, ResourceBundle rb) {
|
public void initialize(URL url, ResourceBundle rb) {
|
||||||
super.initialize(url, rb);
|
super.initialize(url, rb);
|
||||||
|
|
||||||
|
//TODO handle in base class
|
||||||
pm.onViewInitialized();
|
pm.onViewInitialized();
|
||||||
|
|
||||||
|
setupBindings();
|
||||||
|
setupListeners();
|
||||||
|
configTextFieldValidators();
|
||||||
balanceTextField.setup(pm.getWalletFacade(), pm.address.get());
|
balanceTextField.setup(pm.getWalletFacade(), pm.address.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,21 +88,19 @@ public class CreateOfferCB extends CachedViewController {
|
||||||
public void deactivate() {
|
public void deactivate() {
|
||||||
super.deactivate();
|
super.deactivate();
|
||||||
|
|
||||||
|
//TODO handle in base class
|
||||||
pm.deactivate();
|
pm.deactivate();
|
||||||
|
|
||||||
//TODO check that again
|
//TODO check that again
|
||||||
((TradeController) parentController).onCreateOfferViewRemoved();
|
if (parentController != null) ((TradeController) parentController).onCreateOfferViewRemoved();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void activate() {
|
public void activate() {
|
||||||
super.activate();
|
super.activate();
|
||||||
|
|
||||||
|
//TODO handle in base class
|
||||||
pm.activate();
|
pm.activate();
|
||||||
|
|
||||||
setupBindings();
|
|
||||||
setupListeners();
|
|
||||||
setupTextFieldValidators();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -138,7 +137,7 @@ public class CreateOfferCB extends CachedViewController {
|
||||||
|
|
||||||
private void setupListeners() {
|
private void setupListeners() {
|
||||||
volumeTextField.focusedProperty().addListener((o, oldValue, newValue) -> {
|
volumeTextField.focusedProperty().addListener((o, oldValue, newValue) -> {
|
||||||
pm.onFocusOutVolumeTextField(oldValue, newValue, volumeTextField.getText());
|
pm.onFocusOutVolumeTextField(oldValue, newValue);
|
||||||
volumeTextField.setText(pm.volume.get());
|
volumeTextField.setText(pm.volume.get());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -157,15 +156,6 @@ public class CreateOfferCB extends CachedViewController {
|
||||||
minAmountTextField.setText(pm.minAmount.get());
|
minAmountTextField.setText(pm.minAmount.get());
|
||||||
});
|
});
|
||||||
|
|
||||||
pm.needsInputValidation.addListener((o, oldValue, newValue) -> {
|
|
||||||
if (newValue) {
|
|
||||||
amountTextField.reValidate();
|
|
||||||
minAmountTextField.reValidate();
|
|
||||||
volumeTextField.reValidate();
|
|
||||||
priceTextField.reValidate();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
pm.showWarningInvalidBtcDecimalPlaces.addListener((o, oldValue, newValue) -> {
|
pm.showWarningInvalidBtcDecimalPlaces.addListener((o, oldValue, newValue) -> {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
Popups.openWarningPopup("Warning", "The amount you have entered exceeds the number of allowed decimal" +
|
Popups.openWarningPopup("Warning", "The amount you have entered exceeds the number of allowed decimal" +
|
||||||
|
@ -182,11 +172,11 @@ public class CreateOfferCB extends CachedViewController {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
pm.showWarningInvalidBtcFractions.addListener((o, oldValue, newValue) -> {
|
pm.showWarningAdjustedVolume.addListener((o, oldValue, newValue) -> {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
Popups.openWarningPopup("Warning", "The total volume you have entered leads to invalid fractional " +
|
Popups.openWarningPopup("Warning", "The total volume you have entered leads to invalid fractional " +
|
||||||
"Bitcoin amounts.\nThe amount has been adjusted and a new total volume be calculated from it.");
|
"Bitcoin amounts.\nThe amount has been adjusted and a new total volume be calculated from it.");
|
||||||
pm.showWarningInvalidBtcFractions.set(false);
|
pm.showWarningAdjustedVolume.set(false);
|
||||||
volumeTextField.setText(pm.volume.get());
|
volumeTextField.setText(pm.volume.get());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -224,46 +214,21 @@ public class CreateOfferCB extends CachedViewController {
|
||||||
totalFeesTextField.textProperty().bind(pm.totalFees);
|
totalFeesTextField.textProperty().bind(pm.totalFees);
|
||||||
transactionIdTextField.textProperty().bind(pm.transactionId);
|
transactionIdTextField.textProperty().bind(pm.transactionId);
|
||||||
|
|
||||||
|
amountTextField.amountValidationResultProperty().bind(pm.amountValidationResult);
|
||||||
|
minAmountTextField.amountValidationResultProperty().bind(pm.minAmountValidationResult);
|
||||||
|
priceTextField.amountValidationResultProperty().bind(pm.priceValidationResult);
|
||||||
|
volumeTextField.amountValidationResultProperty().bind(pm.volumeValidationResult);
|
||||||
|
|
||||||
placeOfferButton.visibleProperty().bind(pm.isPlaceOfferButtonVisible);
|
placeOfferButton.visibleProperty().bind(pm.isPlaceOfferButtonVisible);
|
||||||
placeOfferButton.disableProperty().bind(pm.isPlaceOfferButtonDisabled);
|
placeOfferButton.disableProperty().bind(pm.isPlaceOfferButtonDisabled);
|
||||||
closeButton.visibleProperty().bind(pm.isCloseButtonVisible);
|
closeButton.visibleProperty().bind(pm.isCloseButtonVisible);
|
||||||
|
|
||||||
//TODO
|
|
||||||
/* progressIndicator.visibleProperty().bind(viewModel.isOfferPlacedScreen);
|
|
||||||
confirmationLabel.visibleProperty().bind(viewModel.isOfferPlacedScreen);
|
|
||||||
txTitleLabel.visibleProperty().bind(viewModel.isOfferPlacedScreen);
|
|
||||||
transactionIdTextField.visibleProperty().bind(viewModel.isOfferPlacedScreen);
|
|
||||||
*/
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
/* placeOfferButton.disableProperty().bind(amountTextField.isValidProperty()
|
|
||||||
.and(minAmountTextField.isValidProperty())
|
|
||||||
.and(volumeTextField.isValidProperty())
|
|
||||||
.and(priceTextField.isValidProperty()).not());*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupTextFieldValidators() {
|
private void configTextFieldValidators() {
|
||||||
Region referenceNode = (Region) amountTextField.getParent();
|
Region referenceNode = (Region) amountTextField.getParent();
|
||||||
|
|
||||||
BtcValidator amountValidator = new BtcValidator();
|
|
||||||
amountTextField.setValidator(amountValidator);
|
|
||||||
amountTextField.setErrorPopupLayoutReference(referenceNode);
|
amountTextField.setErrorPopupLayoutReference(referenceNode);
|
||||||
|
|
||||||
priceTextField.setValidator(new FiatValidator());
|
|
||||||
priceTextField.setErrorPopupLayoutReference(referenceNode);
|
priceTextField.setErrorPopupLayoutReference(referenceNode);
|
||||||
|
|
||||||
volumeTextField.setValidator(new FiatValidator());
|
|
||||||
volumeTextField.setErrorPopupLayoutReference(referenceNode);
|
volumeTextField.setErrorPopupLayoutReference(referenceNode);
|
||||||
|
|
||||||
BtcValidator minAmountValidator = new BtcValidator();
|
|
||||||
minAmountTextField.setValidator(minAmountValidator);
|
|
||||||
|
|
||||||
ValidationHelper.setupMinAmountInRangeOfAmountValidation(amountTextField,
|
|
||||||
minAmountTextField,
|
|
||||||
pm.amount,
|
|
||||||
pm.minAmount,
|
|
||||||
amountValidator,
|
|
||||||
minAmountValidator);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ import org.slf4j.LoggerFactory;
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Domain for that UI element.
|
* Domain for that UI element.
|
||||||
* Note that the create offer domain has a deeper scope in the application domain (TradeManager).
|
* Note that the create offer domain has a deeper scope in the application domain (TradeManager).
|
||||||
* That model is just responsible for the domain specific parts displayed needed in that UI element.
|
* That model is just responsible for the domain specific parts displayed needed in that UI element.
|
||||||
*/
|
*/
|
||||||
|
@ -152,6 +152,15 @@ class CreateOfferModel {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Validation
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
boolean isMinAmountLessOrEqualAmount() {
|
||||||
|
if (minAmountAsCoin != null && amountAsCoin != null)
|
||||||
|
return !minAmountAsCoin.isGreaterThan(amountAsCoin);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Setter/Getter
|
// Setter/Getter
|
||||||
|
|
|
@ -19,6 +19,9 @@ package io.bitsquare.gui.trade.createoffer;
|
||||||
|
|
||||||
import io.bitsquare.btc.WalletFacade;
|
import io.bitsquare.btc.WalletFacade;
|
||||||
import io.bitsquare.gui.util.BSFormatter;
|
import io.bitsquare.gui.util.BSFormatter;
|
||||||
|
import io.bitsquare.gui.util.validation.BtcValidator;
|
||||||
|
import io.bitsquare.gui.util.validation.FiatValidator;
|
||||||
|
import io.bitsquare.gui.util.validation.InputValidator;
|
||||||
import io.bitsquare.locale.Localisation;
|
import io.bitsquare.locale.Localisation;
|
||||||
import io.bitsquare.trade.Direction;
|
import io.bitsquare.trade.Direction;
|
||||||
import io.bitsquare.trade.orderbook.OrderBookFilter;
|
import io.bitsquare.trade.orderbook.OrderBookFilter;
|
||||||
|
@ -46,6 +49,8 @@ class CreateOfferPM {
|
||||||
private static final Logger log = LoggerFactory.getLogger(CreateOfferPM.class);
|
private static final Logger log = LoggerFactory.getLogger(CreateOfferPM.class);
|
||||||
|
|
||||||
private CreateOfferModel model;
|
private CreateOfferModel model;
|
||||||
|
private BtcValidator btcValidator = new BtcValidator();
|
||||||
|
private FiatValidator fiatValidator = new FiatValidator();
|
||||||
|
|
||||||
final StringProperty amount = new SimpleStringProperty();
|
final StringProperty amount = new SimpleStringProperty();
|
||||||
final StringProperty minAmount = new SimpleStringProperty();
|
final StringProperty minAmount = new SimpleStringProperty();
|
||||||
|
@ -69,19 +74,23 @@ class CreateOfferPM {
|
||||||
final BooleanProperty isCloseButtonVisible = new SimpleBooleanProperty();
|
final BooleanProperty isCloseButtonVisible = new SimpleBooleanProperty();
|
||||||
final BooleanProperty isPlaceOfferButtonVisible = new SimpleBooleanProperty(true);
|
final BooleanProperty isPlaceOfferButtonVisible = new SimpleBooleanProperty(true);
|
||||||
final BooleanProperty isPlaceOfferButtonDisabled = new SimpleBooleanProperty();
|
final BooleanProperty isPlaceOfferButtonDisabled = new SimpleBooleanProperty();
|
||||||
final BooleanProperty needsInputValidation = new SimpleBooleanProperty();
|
final BooleanProperty showWarningAdjustedVolume = new SimpleBooleanProperty();
|
||||||
final BooleanProperty showWarningInvalidBtcFractions = new SimpleBooleanProperty();
|
|
||||||
final BooleanProperty showWarningInvalidFiatDecimalPlaces = new SimpleBooleanProperty();
|
final BooleanProperty showWarningInvalidFiatDecimalPlaces = new SimpleBooleanProperty();
|
||||||
final BooleanProperty showWarningInvalidBtcDecimalPlaces = new SimpleBooleanProperty();
|
final BooleanProperty showWarningInvalidBtcDecimalPlaces = new SimpleBooleanProperty();
|
||||||
final BooleanProperty showTransactionPublishedScreen = new SimpleBooleanProperty();
|
final BooleanProperty showTransactionPublishedScreen = new SimpleBooleanProperty();
|
||||||
final BooleanProperty requestPlaceOfferFailed = new SimpleBooleanProperty();
|
final BooleanProperty requestPlaceOfferFailed = new SimpleBooleanProperty();
|
||||||
|
|
||||||
|
final ObjectProperty<InputValidator.ValidationResult> amountValidationResult = new SimpleObjectProperty<>();
|
||||||
|
final ObjectProperty<InputValidator.ValidationResult> minAmountValidationResult = new SimpleObjectProperty<>();
|
||||||
|
final ObjectProperty<InputValidator.ValidationResult> priceValidationResult = new SimpleObjectProperty<>();
|
||||||
|
final ObjectProperty<InputValidator.ValidationResult> volumeValidationResult = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
final ObjectProperty<Coin> totalToPayAsCoin = new SimpleObjectProperty<>();
|
final ObjectProperty<Coin> totalToPayAsCoin = new SimpleObjectProperty<>();
|
||||||
final ObjectProperty<Address> address = new SimpleObjectProperty<>();
|
final ObjectProperty<Address> address = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Constructor
|
// Constructor (called by CB)
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
CreateOfferPM(CreateOfferModel model) {
|
CreateOfferPM(CreateOfferModel model) {
|
||||||
|
@ -90,7 +99,7 @@ class CreateOfferPM {
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Lifecycle
|
// Lifecycle (called by CB)
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
void onViewInitialized() {
|
void onViewInitialized() {
|
||||||
|
@ -134,46 +143,54 @@ class CreateOfferPM {
|
||||||
}
|
}
|
||||||
|
|
||||||
void activate() {
|
void activate() {
|
||||||
|
//TODO handle in base class
|
||||||
model.activate();
|
model.activate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void deactivate() {
|
void deactivate() {
|
||||||
|
//TODO handle in base class
|
||||||
model.deactivate();
|
model.deactivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Public methods
|
// Public API methods (called by CB)
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
void setOrderBookFilter(OrderBookFilter orderBookFilter) {
|
void setOrderBookFilter(OrderBookFilter orderBookFilter) {
|
||||||
model.setDirection(orderBookFilter.getDirection());
|
model.setDirection(orderBookFilter.getDirection());
|
||||||
model.amountAsCoin = orderBookFilter.getAmount();
|
directionLabel.set(model.getDirection() == Direction.BUY ? "Buy:" : "Sell:");
|
||||||
model.minAmountAsCoin = orderBookFilter.getAmount();
|
|
||||||
|
if (orderBookFilter.getAmount() != null) {
|
||||||
|
model.amountAsCoin = orderBookFilter.getAmount();
|
||||||
|
amount.set(formatCoin(model.amountAsCoin));
|
||||||
|
|
||||||
|
model.minAmountAsCoin = orderBookFilter.getAmount();
|
||||||
|
minAmount.set(formatCoin(model.minAmountAsCoin));
|
||||||
|
}
|
||||||
|
|
||||||
// TODO use Fiat in orderBookFilter
|
// TODO use Fiat in orderBookFilter
|
||||||
model.priceAsFiat = parseToFiatWith2Decimals(String.valueOf(orderBookFilter.getPrice()));
|
if (orderBookFilter.getPrice() != 0) {
|
||||||
|
model.priceAsFiat = parseToFiatWith2Decimals(String.valueOf(orderBookFilter.getPrice()));
|
||||||
|
price.set(formatFiat(model.priceAsFiat));
|
||||||
|
}
|
||||||
|
|
||||||
directionLabel.set(model.getDirection() == Direction.BUY ? "Buy:" : "Sell:");
|
|
||||||
amount.set(formatCoin(model.amountAsCoin));
|
|
||||||
minAmount.set(formatCoin(model.minAmountAsCoin));
|
|
||||||
price.set(formatFiat(model.priceAsFiat));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// View Events
|
// UI actions (called by CB)
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
void placeOffer() {
|
void placeOffer() {
|
||||||
model.amountAsCoin = parseToCoinWith4Decimals(amount.get());
|
if (allInputsValid()) {
|
||||||
model.minAmountAsCoin = parseToCoinWith4Decimals(minAmount.get());
|
|
||||||
model.priceAsFiat = parseToFiatWith2Decimals(price.get());
|
|
||||||
model.minAmountAsCoin = parseToCoinWith4Decimals(minAmount.get());
|
|
||||||
|
|
||||||
needsInputValidation.set(true);
|
model.amountAsCoin = parseToCoinWith4Decimals(amount.get());
|
||||||
|
model.minAmountAsCoin = parseToCoinWith4Decimals(minAmount.get());
|
||||||
|
model.priceAsFiat = parseToFiatWith2Decimals(price.get());
|
||||||
|
model.minAmountAsCoin = parseToCoinWith4Decimals(minAmount.get());
|
||||||
|
|
||||||
if (inputValid()) {
|
|
||||||
model.placeOffer();
|
model.placeOffer();
|
||||||
isPlaceOfferButtonDisabled.set(true);
|
isPlaceOfferButtonDisabled.set(true);
|
||||||
isPlaceOfferButtonVisible.set(true);
|
isPlaceOfferButtonVisible.set(true);
|
||||||
|
@ -183,79 +200,100 @@ class CreateOfferPM {
|
||||||
void close() {
|
void close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
// UI events (called by CB)
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
void setupInputListeners() {
|
// when focus out we do validation and apply the data to the model
|
||||||
|
|
||||||
// bindBidirectional for amount, price, volume and minAmount
|
|
||||||
amount.addListener(ov -> {
|
|
||||||
model.amountAsCoin = parseToCoinWith4Decimals(amount.get());
|
|
||||||
calculateVolume();
|
|
||||||
calculateTotalToPay();
|
|
||||||
calculateCollateral();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
price.addListener(ov -> {
|
|
||||||
model.priceAsFiat = parseToFiatWith2Decimals(price.get());
|
|
||||||
calculateVolume();
|
|
||||||
calculateTotalToPay();
|
|
||||||
calculateCollateral();
|
|
||||||
});
|
|
||||||
|
|
||||||
volume.addListener(ov -> {
|
|
||||||
model.volumeAsFiat = parseToFiatWith2Decimals(volume.get());
|
|
||||||
calculateAmount();
|
|
||||||
calculateTotalToPay();
|
|
||||||
calculateCollateral();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void onFocusOutAmountTextField(Boolean oldValue, Boolean newValue) {
|
void onFocusOutAmountTextField(Boolean oldValue, Boolean newValue) {
|
||||||
|
|
||||||
if (oldValue && !newValue) {
|
if (oldValue && !newValue) {
|
||||||
showWarningInvalidBtcDecimalPlaces.set(!hasBtcValidDecimals(amount.get()));
|
InputValidator.ValidationResult result = isBtcInputValid(amount.get());
|
||||||
model.amountAsCoin = parseToCoinWith4Decimals(amount.get());
|
boolean isValid = result.isValid;
|
||||||
amount.set(formatCoin(model.amountAsCoin));
|
amountValidationResult.set(result);
|
||||||
calculateVolume();
|
if (isValid) {
|
||||||
|
showWarningInvalidBtcDecimalPlaces.set(!hasBtcValidDecimals(amount.get()));
|
||||||
|
// only allow max 4 decimal places for btc values
|
||||||
|
model.amountAsCoin = parseToCoinWith4Decimals(amount.get());
|
||||||
|
// reformat input to general btc format
|
||||||
|
amount.set(formatCoin(model.amountAsCoin));
|
||||||
|
calculateVolume();
|
||||||
|
|
||||||
|
if (!model.isMinAmountLessOrEqualAmount()) {
|
||||||
|
amountValidationResult.set(new InputValidator.ValidationResult(false,
|
||||||
|
"Amount cannot be smaller than minimum amount."));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
amountValidationResult.set(result);
|
||||||
|
if (minAmount.get() != null)
|
||||||
|
minAmountValidationResult.set(isBtcInputValid(minAmount.get()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFocusOutMinAmountTextField(Boolean oldValue, Boolean newValue) {
|
void onFocusOutMinAmountTextField(Boolean oldValue, Boolean newValue) {
|
||||||
|
|
||||||
if (oldValue && !newValue) {
|
if (oldValue && !newValue) {
|
||||||
showWarningInvalidBtcDecimalPlaces.set(!hasBtcValidDecimals(minAmount.get()));
|
InputValidator.ValidationResult result = isBtcInputValid(minAmount.get());
|
||||||
model.minAmountAsCoin = parseToCoinWith4Decimals(minAmount.get());
|
boolean isValid = result.isValid;
|
||||||
minAmount.set(formatCoin(model.minAmountAsCoin));
|
minAmountValidationResult.set(result);
|
||||||
}
|
if (isValid) {
|
||||||
}
|
showWarningInvalidBtcDecimalPlaces.set(!hasBtcValidDecimals(minAmount.get()));
|
||||||
|
model.minAmountAsCoin = parseToCoinWith4Decimals(minAmount.get());
|
||||||
|
minAmount.set(formatCoin(model.minAmountAsCoin));
|
||||||
|
|
||||||
void onFocusOutVolumeTextField(Boolean oldValue, Boolean newValue, String volumeTextFieldText) {
|
if (!model.isMinAmountLessOrEqualAmount()) {
|
||||||
if (oldValue && !newValue) {
|
minAmountValidationResult.set(new InputValidator.ValidationResult(false,
|
||||||
showWarningInvalidFiatDecimalPlaces.set(!hasFiatValidDecimals(volume.get()));
|
"Minimum amount cannot be larger than amount."));
|
||||||
model.volumeAsFiat = parseToFiatWith2Decimals(volume.get());
|
}
|
||||||
volume.set(formatFiat(model.volumeAsFiat));
|
else {
|
||||||
calculateAmount();
|
minAmountValidationResult.set(result);
|
||||||
|
if (amount.get() != null)
|
||||||
showWarningInvalidBtcFractions.set(!formatFiat(parseToFiatWith2Decimals(volumeTextFieldText)).equals
|
amountValidationResult.set(isBtcInputValid(amount.get()));
|
||||||
(volume.get()));
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFocusOutPriceTextField(Boolean oldValue, Boolean newValue) {
|
void onFocusOutPriceTextField(Boolean oldValue, Boolean newValue) {
|
||||||
if (oldValue && !newValue) {
|
if (oldValue && !newValue) {
|
||||||
showWarningInvalidFiatDecimalPlaces.set(!hasFiatValidDecimals(price.get()));
|
InputValidator.ValidationResult result = isFiatInputValid(price.get());
|
||||||
model.priceAsFiat = parseToFiatWith2Decimals(price.get());
|
boolean isValid = result.isValid;
|
||||||
price.set(formatFiat(model.priceAsFiat));
|
priceValidationResult.set(result);
|
||||||
calculateVolume();
|
if (isValid) {
|
||||||
|
showWarningInvalidFiatDecimalPlaces.set(!hasFiatValidDecimals(price.get()));
|
||||||
|
model.priceAsFiat = parseToFiatWith2Decimals(price.get());
|
||||||
|
price.set(formatFiat(model.priceAsFiat));
|
||||||
|
calculateVolume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onFocusOutVolumeTextField(Boolean oldValue, Boolean newValue) {
|
||||||
|
if (oldValue && !newValue) {
|
||||||
|
InputValidator.ValidationResult result = isBtcInputValid(volume.get());
|
||||||
|
boolean isValid = result.isValid;
|
||||||
|
volumeValidationResult.set(result);
|
||||||
|
if (isValid) {
|
||||||
|
String origVolume = volume.get();
|
||||||
|
showWarningInvalidFiatDecimalPlaces.set(!hasFiatValidDecimals(volume.get()));
|
||||||
|
model.volumeAsFiat = parseToFiatWith2Decimals(volume.get());
|
||||||
|
|
||||||
|
volume.set(formatFiat(model.volumeAsFiat));
|
||||||
|
calculateAmount();
|
||||||
|
|
||||||
|
// must be after calculateAmount (btc value has been adjusted in case the calculation leads to
|
||||||
|
// invalid decimal places for the amount value
|
||||||
|
showWarningAdjustedVolume.set(!formatFiat(parseToFiatWith2Decimals(origVolume)).equals(volume.get()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Getters
|
// Getters (called by CB)
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
WalletFacade getWalletFacade() {
|
WalletFacade getWalletFacade() {
|
||||||
|
@ -267,11 +305,45 @@ class CreateOfferPM {
|
||||||
// Private
|
// Private
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private boolean inputValid() {
|
private void setupInputListeners() {
|
||||||
//TODO
|
|
||||||
return true;
|
// bindBidirectional for amount, price, volume and minAmount
|
||||||
|
// We do volume/amount calculation during input
|
||||||
|
amount.addListener((ov, oldValue, newValue) -> {
|
||||||
|
if (isBtcInputValid(newValue).isValid) {
|
||||||
|
model.amountAsCoin = parseToCoinWith4Decimals(newValue);
|
||||||
|
calculateVolume();
|
||||||
|
calculateTotalToPay();
|
||||||
|
calculateCollateral();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
price.addListener((ov, oldValue, newValue) -> {
|
||||||
|
if (isFiatInputValid(newValue).isValid) {
|
||||||
|
model.priceAsFiat = parseToFiatWith2Decimals(newValue);
|
||||||
|
calculateVolume();
|
||||||
|
calculateTotalToPay();
|
||||||
|
calculateCollateral();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
volume.addListener((ov, oldValue, newValue) -> {
|
||||||
|
if (isFiatInputValid(newValue).isValid) {
|
||||||
|
model.volumeAsFiat = parseToFiatWith2Decimals(newValue);
|
||||||
|
calculateAmount();
|
||||||
|
calculateTotalToPay();
|
||||||
|
calculateCollateral();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean allInputsValid() {
|
||||||
|
return isBtcInputValid(amount.get()).isValid && isFiatInputValid(price.get()).isValid && isFiatInputValid
|
||||||
|
(volume.get()).isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO move to model
|
||||||
private void calculateVolume() {
|
private void calculateVolume() {
|
||||||
model.amountAsCoin = parseToCoinWith4Decimals(amount.get());
|
model.amountAsCoin = parseToCoinWith4Decimals(amount.get());
|
||||||
model.priceAsFiat = parseToFiatWith2Decimals(price.get());
|
model.priceAsFiat = parseToFiatWith2Decimals(price.get());
|
||||||
|
@ -282,6 +354,7 @@ class CreateOfferPM {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO move to model
|
||||||
private void calculateAmount() {
|
private void calculateAmount() {
|
||||||
model.volumeAsFiat = parseToFiatWith2Decimals(volume.get());
|
model.volumeAsFiat = parseToFiatWith2Decimals(volume.get());
|
||||||
model.priceAsFiat = parseToFiatWith2Decimals(price.get());
|
model.priceAsFiat = parseToFiatWith2Decimals(price.get());
|
||||||
|
@ -295,8 +368,20 @@ class CreateOfferPM {
|
||||||
calculateTotalToPay();
|
calculateTotalToPay();
|
||||||
calculateCollateral();
|
calculateCollateral();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!model.isMinAmountLessOrEqualAmount()) {
|
||||||
|
amountValidationResult.set(new InputValidator.ValidationResult(false,
|
||||||
|
"Amount cannot be smaller than minimum amount."));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (amount.get() != null)
|
||||||
|
amountValidationResult.set(isBtcInputValid(amount.get()));
|
||||||
|
if (minAmount.get() != null)
|
||||||
|
minAmountValidationResult.set(isBtcInputValid(minAmount.get()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO move to model
|
||||||
private void calculateTotalToPay() {
|
private void calculateTotalToPay() {
|
||||||
calculateCollateral();
|
calculateCollateral();
|
||||||
|
|
||||||
|
@ -307,10 +392,27 @@ class CreateOfferPM {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO move to model
|
||||||
private void calculateCollateral() {
|
private void calculateCollateral() {
|
||||||
if (model.amountAsCoin != null) {
|
if (model.amountAsCoin != null) {
|
||||||
model.collateralAsCoin = model.amountAsCoin.multiply(model.collateralAsLong.get()).divide(1000);
|
model.collateralAsCoin = model.amountAsCoin.multiply(model.collateralAsLong.get()).divide(1000);
|
||||||
collateral.set(BSFormatter.formatCoinWithCode(model.collateralAsCoin));
|
collateral.set(BSFormatter.formatCoinWithCode(model.collateralAsCoin));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Package scope for testing
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
InputValidator.ValidationResult isBtcInputValid(String input) {
|
||||||
|
|
||||||
|
return btcValidator.validate(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputValidator.ValidationResult isFiatInputValid(String input) {
|
||||||
|
|
||||||
|
return fiatValidator.validate(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,8 +80,7 @@ public class BtcValidator extends NumberValidator {
|
||||||
if (satoshis.scale() > 0)
|
if (satoshis.scale() > 0)
|
||||||
return new ValidationResult(
|
return new ValidationResult(
|
||||||
false,
|
false,
|
||||||
"Input results in a Bitcoin value with a fraction of the smallest unit (Satoshi).",
|
"Input results in a Bitcoin value with a fraction of the smallest unit (Satoshi).");
|
||||||
ErrorType.FRACTIONAL_SATOSHI);
|
|
||||||
else
|
else
|
||||||
return new ValidationResult(true);
|
return new ValidationResult(true);
|
||||||
}
|
}
|
||||||
|
@ -92,8 +91,7 @@ public class BtcValidator extends NumberValidator {
|
||||||
if (satoshis.longValue() > NetworkParameters.MAX_MONEY.longValue())
|
if (satoshis.longValue() > NetworkParameters.MAX_MONEY.longValue())
|
||||||
return new ValidationResult(
|
return new ValidationResult(
|
||||||
false,
|
false,
|
||||||
"Input larger as maximum possible Bitcoin value is not allowed.",
|
"Input larger as maximum possible Bitcoin value is not allowed.");
|
||||||
ErrorType.EXCEEDS_MAX_BTC_VALUE);
|
|
||||||
else
|
else
|
||||||
return new ValidationResult(true);
|
return new ValidationResult(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,8 +65,7 @@ public class FiatValidator extends NumberValidator {
|
||||||
if (d < MIN_FIAT_VALUE)
|
if (d < MIN_FIAT_VALUE)
|
||||||
return new ValidationResult(
|
return new ValidationResult(
|
||||||
false,
|
false,
|
||||||
"Input smaller as minimum possible Fiat value is not allowed..",
|
"Input smaller as minimum possible Fiat value is not allowed..");
|
||||||
ErrorType.UNDERCUT_MIN_FIAT_VALUE);
|
|
||||||
else
|
else
|
||||||
return new ValidationResult(true);
|
return new ValidationResult(true);
|
||||||
}
|
}
|
||||||
|
@ -76,8 +75,7 @@ public class FiatValidator extends NumberValidator {
|
||||||
if (d > MAX_FIAT_VALUE)
|
if (d > MAX_FIAT_VALUE)
|
||||||
return new ValidationResult(
|
return new ValidationResult(
|
||||||
false,
|
false,
|
||||||
"Input larger as maximum possible Fiat value is not allowed.",
|
"Input larger as maximum possible Fiat value is not allowed.");
|
||||||
ErrorType.EXCEEDS_MAX_FIAT_VALUE);
|
|
||||||
else
|
else
|
||||||
return new ValidationResult(true);
|
return new ValidationResult(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ public abstract class InputValidator {
|
||||||
|
|
||||||
protected ValidationResult validateIfNotEmpty(String input) {
|
protected ValidationResult validateIfNotEmpty(String input) {
|
||||||
if (input == null || input.length() == 0)
|
if (input == null || input.length() == 0)
|
||||||
return new ValidationResult(false, "Empty input is not allowed.", ErrorType.EMPTY_INPUT);
|
return new ValidationResult(false, "Empty input is not allowed.");
|
||||||
else
|
else
|
||||||
return new ValidationResult(true);
|
return new ValidationResult(true);
|
||||||
}
|
}
|
||||||
|
@ -54,21 +54,6 @@ public abstract class InputValidator {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// ErrorType
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
public enum ErrorType {
|
|
||||||
EMPTY_INPUT,
|
|
||||||
NOT_A_NUMBER,
|
|
||||||
ZERO_NUMBER,
|
|
||||||
NEGATIVE_NUMBER,
|
|
||||||
FRACTIONAL_SATOSHI,
|
|
||||||
EXCEEDS_MAX_FIAT_VALUE, UNDERCUT_MIN_FIAT_VALUE, AMOUNT_LESS_THAN_MIN_AMOUNT,
|
|
||||||
MIN_AMOUNT_LARGER_THAN_MIN_AMOUNT, EXCEEDS_MAX_BTC_VALUE
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// ValidationResult
|
// ValidationResult
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -76,16 +61,14 @@ public abstract class InputValidator {
|
||||||
public static class ValidationResult {
|
public static class ValidationResult {
|
||||||
public final boolean isValid;
|
public final boolean isValid;
|
||||||
public final String errorMessage;
|
public final String errorMessage;
|
||||||
public final ErrorType errorType;
|
|
||||||
|
|
||||||
public ValidationResult(boolean isValid, String errorMessage, ErrorType errorType) {
|
public ValidationResult(boolean isValid, String errorMessage) {
|
||||||
this.isValid = isValid;
|
this.isValid = isValid;
|
||||||
this.errorMessage = errorMessage;
|
this.errorMessage = errorMessage;
|
||||||
this.errorType = errorType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidationResult(boolean isValid) {
|
public ValidationResult(boolean isValid) {
|
||||||
this(isValid, null, null);
|
this(isValid, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValidationResult and(ValidationResult next) {
|
public ValidationResult and(ValidationResult next) {
|
||||||
|
@ -100,7 +83,6 @@ public abstract class InputValidator {
|
||||||
return "ValidationResult{" +
|
return "ValidationResult{" +
|
||||||
"isValid=" + isValid +
|
"isValid=" + isValid +
|
||||||
", errorMessage='" + errorMessage + '\'' +
|
", errorMessage='" + errorMessage + '\'' +
|
||||||
", errorType=" + errorType +
|
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,20 +45,20 @@ public abstract class NumberValidator extends InputValidator {
|
||||||
Double.parseDouble(input);
|
Double.parseDouble(input);
|
||||||
return new ValidationResult(true);
|
return new ValidationResult(true);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return new ValidationResult(false, "Input is not a valid number.", ErrorType.NOT_A_NUMBER);
|
return new ValidationResult(false, "Input is not a valid number.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ValidationResult validateIfNotZero(String input) {
|
protected ValidationResult validateIfNotZero(String input) {
|
||||||
if (Double.parseDouble(input) == 0)
|
if (Double.parseDouble(input) == 0)
|
||||||
return new ValidationResult(false, "Input of 0 is not allowed.", ErrorType.ZERO_NUMBER);
|
return new ValidationResult(false, "Input of 0 is not allowed.");
|
||||||
else
|
else
|
||||||
return new ValidationResult(true);
|
return new ValidationResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ValidationResult validateIfNotNegative(String input) {
|
protected ValidationResult validateIfNotNegative(String input) {
|
||||||
if (Double.parseDouble(input) < 0)
|
if (Double.parseDouble(input) < 0)
|
||||||
return new ValidationResult(false, "A negative value is not allowed.", ErrorType.NEGATIVE_NUMBER);
|
return new ValidationResult(false, "A negative value is not allowed.");
|
||||||
else
|
else
|
||||||
return new ValidationResult(true);
|
return new ValidationResult(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,117 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Bitsquare.
|
|
||||||
*
|
|
||||||
* Bitsquare is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.bitsquare.gui.util.validation;
|
|
||||||
|
|
||||||
import io.bitsquare.gui.components.ValidatingTextField;
|
|
||||||
|
|
||||||
import javafx.beans.property.StringProperty;
|
|
||||||
import javafx.scene.control.*;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class for setting up the validation and dependencies for minAmount and Amount.
|
|
||||||
* TODO Might be improved but does the job for now...
|
|
||||||
*/
|
|
||||||
public class ValidationHelper {
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(ValidationHelper.class);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles validation between minAmount and amount fields
|
|
||||||
* Min amount must not be larger as amount.
|
|
||||||
* Handles focus out events to display always the error popup from the field where the focus out happened.
|
|
||||||
*/
|
|
||||||
public static void setupMinAmountInRangeOfAmountValidation(ValidatingTextField amountTextField,
|
|
||||||
ValidatingTextField minAmountTextField,
|
|
||||||
StringProperty amount,
|
|
||||||
StringProperty minAmount,
|
|
||||||
BtcValidator amountValidator,
|
|
||||||
BtcValidator minAmountValidator) {
|
|
||||||
|
|
||||||
|
|
||||||
amountTextField.focusedProperty().addListener((ov, oldValue, newValue) -> {
|
|
||||||
// only on focus out and ignore focus loss from window
|
|
||||||
if (!newValue && amountTextField.getScene() != null && amountTextField.getScene().getWindow().isFocused())
|
|
||||||
validateMinAmount(amountTextField,
|
|
||||||
minAmountTextField,
|
|
||||||
amount,
|
|
||||||
minAmount,
|
|
||||||
amountValidator,
|
|
||||||
minAmountValidator,
|
|
||||||
amountTextField);
|
|
||||||
});
|
|
||||||
|
|
||||||
minAmountTextField.focusedProperty().addListener((ov, oldValue, newValue) -> {
|
|
||||||
// only on focus out and ignore focus loss from window
|
|
||||||
if (!newValue && minAmountTextField.getScene() != null &&
|
|
||||||
minAmountTextField.getScene().getWindow().isFocused())
|
|
||||||
validateMinAmount(amountTextField,
|
|
||||||
minAmountTextField,
|
|
||||||
amount,
|
|
||||||
minAmount,
|
|
||||||
amountValidator,
|
|
||||||
minAmountValidator,
|
|
||||||
minAmountTextField);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void validateMinAmount(ValidatingTextField amountTextField,
|
|
||||||
ValidatingTextField minAmountTextField,
|
|
||||||
StringProperty amount,
|
|
||||||
StringProperty minAmount,
|
|
||||||
BtcValidator amountValidator,
|
|
||||||
BtcValidator minAmountValidator,
|
|
||||||
TextField currentTextField) {
|
|
||||||
amountValidator.overrideResult(null);
|
|
||||||
String amountCleaned = amount.get() != null ? amount.get().replace(",", ".").trim() : "0";
|
|
||||||
String minAmountCleaned = minAmount.get() != null ? minAmount.get().replace(",", ".").trim() : "0";
|
|
||||||
|
|
||||||
if (!amountValidator.validate(amountCleaned).isValid)
|
|
||||||
return;
|
|
||||||
|
|
||||||
minAmountValidator.overrideResult(null);
|
|
||||||
if (!minAmountValidator.validate(minAmountCleaned).isValid)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (currentTextField == amountTextField) {
|
|
||||||
if (Double.parseDouble(amountCleaned) < Double.parseDouble(minAmountCleaned)) {
|
|
||||||
amountValidator.overrideResult(new NumberValidator.ValidationResult(false,
|
|
||||||
"Amount cannot be smaller than minimum amount.",
|
|
||||||
NumberValidator.ErrorType.AMOUNT_LESS_THAN_MIN_AMOUNT));
|
|
||||||
amountTextField.reValidate();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
amountValidator.overrideResult(null);
|
|
||||||
minAmountTextField.reValidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (currentTextField == minAmountTextField) {
|
|
||||||
if (Double.parseDouble(minAmountCleaned) > Double.parseDouble(amountCleaned)) {
|
|
||||||
minAmountValidator.overrideResult(new NumberValidator.ValidationResult(false,
|
|
||||||
"Minimum amount cannot be larger than amount.",
|
|
||||||
NumberValidator.ErrorType.MIN_AMOUNT_LARGER_THAN_MIN_AMOUNT));
|
|
||||||
minAmountTextField.reValidate();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
minAmountValidator.overrideResult(null);
|
|
||||||
amountTextField.reValidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,15 +10,19 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
// TODO update to open source file when its released
|
// TODO update to open source file when its released
|
||||||
|
|
||||||
/** Manages the directory where the app stores all its files. */
|
/**
|
||||||
|
* Manages the directory where the app stores all its files.
|
||||||
|
*/
|
||||||
public class AppDirectory {
|
public class AppDirectory {
|
||||||
public static Path getUserDataDir() {
|
public static Path getUserDataDir() {
|
||||||
String os = System.getProperty("os.name").toLowerCase();
|
String os = System.getProperty("os.name").toLowerCase();
|
||||||
if (os.contains("win")) {
|
if (os.contains("win")) {
|
||||||
return Paths.get(System.getenv("APPDATA"));
|
return Paths.get(System.getenv("APPDATA"));
|
||||||
} else if (os.contains("mac")) {
|
}
|
||||||
|
else if (os.contains("mac")) {
|
||||||
return Paths.get(System.getProperty("user.home"), "Library", "Application Support");
|
return Paths.get(System.getProperty("user.home"), "Library", "Application Support");
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
// Linux and other similar systems, we hope (not Android).
|
// Linux and other similar systems, we hope (not Android).
|
||||||
return Paths.get(System.getProperty("user.home"), ".local", "share");
|
return Paths.get(System.getProperty("user.home"), ".local", "share");
|
||||||
}
|
}
|
||||||
|
@ -30,7 +34,7 @@ public class AppDirectory {
|
||||||
|
|
||||||
public static Path initAppDir(String appName) throws IOException {
|
public static Path initAppDir(String appName) throws IOException {
|
||||||
AppDirectory.appName = appName;
|
AppDirectory.appName = appName;
|
||||||
|
|
||||||
Path dir = dir();
|
Path dir = dir();
|
||||||
if (!Files.exists(dir))
|
if (!Files.exists(dir))
|
||||||
Files.createDirectory(dir);
|
Files.createDirectory(dir);
|
||||||
|
@ -39,7 +43,7 @@ public class AppDirectory {
|
||||||
return dir;
|
return dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String appName;
|
private static String appName = "";
|
||||||
|
|
||||||
private static Path dir;
|
private static Path dir;
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
-->
|
-->
|
||||||
</root>
|
</root>
|
||||||
|
|
||||||
<logger name="io.bitsquare" level="WARN"/>
|
<logger name="io.bitsquare" level="TRACE"/>
|
||||||
|
|
||||||
<logger name="com.google.bitcoin" level="WARN"/>
|
<logger name="com.google.bitcoin" level="WARN"/>
|
||||||
<logger name="net.tomp2p" level="WARN"/>
|
<logger name="net.tomp2p" level="WARN"/>
|
||||||
|
|
|
@ -22,10 +22,12 @@ import io.bitsquare.gui.util.BSFormatter;
|
||||||
import io.bitsquare.locale.Country;
|
import io.bitsquare.locale.Country;
|
||||||
|
|
||||||
import com.google.bitcoin.core.Coin;
|
import com.google.bitcoin.core.Coin;
|
||||||
|
import com.google.bitcoin.core.NetworkParameters;
|
||||||
import com.google.bitcoin.utils.Fiat;
|
import com.google.bitcoin.utils.Fiat;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -36,15 +38,71 @@ import static org.junit.Assert.*;
|
||||||
public class CreateOfferPMTest {
|
public class CreateOfferPMTest {
|
||||||
private static final Logger log = LoggerFactory.getLogger(CreateOfferPMTest.class);
|
private static final Logger log = LoggerFactory.getLogger(CreateOfferPMTest.class);
|
||||||
|
|
||||||
@Test
|
private CreateOfferModel model;
|
||||||
public void testBindings() {
|
private CreateOfferPM presenter;
|
||||||
CreateOfferModel model = new CreateOfferModel(null, null, null, null);
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
model = new CreateOfferModel(null, null, null, null);
|
||||||
|
|
||||||
BSFormatter.setLocale(Locale.US);
|
BSFormatter.setLocale(Locale.US);
|
||||||
BSFormatter.setFiatCurrencyCode("USD");
|
BSFormatter.setFiatCurrencyCode("USD");
|
||||||
|
|
||||||
CreateOfferPM presenter = new CreateOfferPM(model);
|
presenter = new CreateOfferPM(model);
|
||||||
presenter.onViewInitialized();
|
presenter.onViewInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsBtcInputValid() {
|
||||||
|
assertTrue(presenter.isBtcInputValid("1").isValid);
|
||||||
|
assertTrue(presenter.isBtcInputValid("1,1").isValid);
|
||||||
|
assertTrue(presenter.isBtcInputValid("1.1").isValid);
|
||||||
|
assertTrue(presenter.isBtcInputValid(",1").isValid);
|
||||||
|
assertTrue(presenter.isBtcInputValid(".1").isValid);
|
||||||
|
assertTrue(presenter.isBtcInputValid("0.12345678").isValid);
|
||||||
|
assertTrue(presenter.isBtcInputValid(Coin.SATOSHI.toPlainString()).isValid);
|
||||||
|
assertTrue(presenter.isBtcInputValid(NetworkParameters.MAX_MONEY.toPlainString()).isValid);
|
||||||
|
|
||||||
|
assertFalse(presenter.isBtcInputValid(null).isValid);
|
||||||
|
assertFalse(presenter.isBtcInputValid("").isValid);
|
||||||
|
assertFalse(presenter.isBtcInputValid("0").isValid);
|
||||||
|
assertFalse(presenter.isBtcInputValid("0.0").isValid);
|
||||||
|
assertFalse(presenter.isBtcInputValid("0,1,1").isValid);
|
||||||
|
assertFalse(presenter.isBtcInputValid("0.1.1").isValid);
|
||||||
|
assertFalse(presenter.isBtcInputValid("1,000.1").isValid);
|
||||||
|
assertFalse(presenter.isBtcInputValid("1.000,1").isValid);
|
||||||
|
assertFalse(presenter.isBtcInputValid("0.123456789").isValid);
|
||||||
|
assertFalse(presenter.isBtcInputValid("-1").isValid);
|
||||||
|
assertFalse(presenter.isBtcInputValid(String.valueOf(NetworkParameters.MAX_MONEY.longValue() + Coin.SATOSHI
|
||||||
|
.longValue())).isValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsFiatInputValid() {
|
||||||
|
assertTrue(presenter.isFiatInputValid("1").isValid);
|
||||||
|
assertTrue(presenter.isFiatInputValid("1,1").isValid);
|
||||||
|
assertTrue(presenter.isFiatInputValid("1.1").isValid);
|
||||||
|
assertTrue(presenter.isFiatInputValid(",1").isValid);
|
||||||
|
assertTrue(presenter.isFiatInputValid(".1").isValid);
|
||||||
|
assertTrue(presenter.isFiatInputValid("0.01").isValid);
|
||||||
|
assertTrue(presenter.isFiatInputValid("1000000.00").isValid);
|
||||||
|
|
||||||
|
assertFalse(presenter.isFiatInputValid(null).isValid);
|
||||||
|
assertFalse(presenter.isFiatInputValid("").isValid);
|
||||||
|
assertFalse(presenter.isFiatInputValid("0").isValid);
|
||||||
|
assertFalse(presenter.isFiatInputValid("-1").isValid);
|
||||||
|
assertFalse(presenter.isFiatInputValid("0.0").isValid);
|
||||||
|
assertFalse(presenter.isFiatInputValid("0,1,1").isValid);
|
||||||
|
assertFalse(presenter.isFiatInputValid("0.1.1").isValid);
|
||||||
|
assertFalse(presenter.isFiatInputValid("1,000.1").isValid);
|
||||||
|
assertFalse(presenter.isFiatInputValid("1.000,1").isValid);
|
||||||
|
assertFalse(presenter.isFiatInputValid("0.009").isValid);
|
||||||
|
assertFalse(presenter.isFiatInputValid("1000000.01").isValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBindings() {
|
||||||
|
|
||||||
|
|
||||||
model.collateralAsLong.set(100);
|
model.collateralAsLong.set(100);
|
||||||
presenter.price.set("500");
|
presenter.price.set("500");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue